diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 51bba2cac74..cd918eadeeb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,7 +11,6 @@ _Optional: add details about technical approach, solutions etc._ _Optional: reference dependencies to other pull requests etc._ - ### Testing _Describe how to test._ diff --git a/.github/workflows/_reusable_app_release.yml b/.github/workflows/_reusable_app_release.yml index 1794715c159..266c29bdfe7 100644 --- a/.github/workflows/_reusable_app_release.yml +++ b/.github/workflows/_reusable_app_release.yml @@ -7,6 +7,8 @@ on: is_cloud_build: type: boolean default: true + countly_enabled: + type: boolean datadog_enabled: type: boolean default: false @@ -91,7 +93,7 @@ jobs: WIRE_INTERNAL_GITHUB_USER: ${{ secrets.WIRE_INTERNAL_GITHUB_USER }} WIRE_INTERNAL_GITHUB_TOKEN: ${{ secrets.WIRE_INTERNAL_GITHUB_TOKEN }} APPCENTER_API_TOKEN: ${{ secrets.APPCENTER_API_TOKEN }} - APPCENTER_OWNER_NAME: ${{ secrets.APPCENTER_OWNER_NAME }} + APPCENTER_OWNER_NAME: "Wire" S3_BUCKET: "z-lohika" AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -115,6 +117,7 @@ jobs: PLAYGROUND_TESTFLIGHT_LINK: ${{ secrets.PLAYGROUND_TESTFLIGHT_LINK }} COUNTLY_PRODUCTION_KEY: ${{ secrets.COUNTLY_PRODUCTION_KEY }} COUNTLY_INTERNAL_KEY: ${{ secrets.COUNTLY_INTERNAL_KEY }} + ENABLE_COUNTLY: ${{ inputs.countly_enabled }} SLACK_WEBHOOK_URL: ${{ secrets.WIRE_IOS_CI_WEBHOOK }} SKIP_SECURITY_TESTS: ${{ inputs.skip_security_tests }} SEND_TO_EXTERNALS: ${{ inputs.distribute_externals }} @@ -159,7 +162,7 @@ jobs: uses: actions/cache@v3 with: path: ${{ env.PACKAGES_DIR }} - key: ${{ runner.os }}-swiftpm-project-${{ hashFiles('**/*.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}-${{ env.ENABLE_DATADOG }} + key: ${{ runner.os }}-swiftpm-project-${{ hashFiles('**/*.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}-${{ env.ENABLE_COUNTLY }}-${{ env.ENABLE_DATADOG }} # Resolve Swift Package Dependencies - name: Resolve Swift Package Dependencies @@ -186,7 +189,7 @@ jobs: uses: actions/upload-artifact@v4 if: failure() with: - name: build-logs + name: build-logs (${{ github.run_id }} - ${{ github.run_attempt}}) path: | /Users/runner/Library/Developer/Xcode/DerivedData/**/Logs/** ~/Library/Logs/DiagnosticReports/** @@ -196,7 +199,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: post-build-env-${{ inputs.fastlane_action }} + name: post-build-env-${{ inputs.fastlane_action }} (${{ github.run_id }} - ${{ github.run_attempt}}) path: | **/.post_build/*.env - name: Load .env file @@ -213,7 +216,7 @@ jobs: uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} - text: "**${{ env.APP_NAME }}** (version: ${{ env.BUILD_VERSION }} build: ${{ env.BUILD_NUMBER }}) is ready to test 🚀\n**DATADOG_ENABLED:** ${{ env.ENABLE_DATADOG }}\n**CHANGELOG:** ${{ needs.changelog.outputs.changelog-url }}\n**Tap on iOS device to install:** ${{ env.BUILD_INSTALL_LINK }}\n**AWS S3 Paths:**\n`${{ steps.base64-decoded-S3_PATHS.outputs.decoded }}`\n**Triggered by:** ${{ github.triggering_actor }}\n**Build log:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n" + text: "**${{ env.APP_NAME }}** (version: ${{ env.BUILD_VERSION }} build: ${{ env.BUILD_NUMBER }}) is ready to test 🚀\n**COUNTLY_ENABLED:** ${{ env.ENABLE_COUNTLY }}\n**DATADOG_ENABLED:** ${{ env.ENABLE_DATADOG }}\n**CHANGELOG:** ${{ needs.changelog.outputs.changelog-url }}\n**Tap on iOS device to install:** ${{ env.BUILD_INSTALL_LINK }}\n**AWS S3 Paths:**\n`${{ steps.base64-decoded-S3_PATHS.outputs.decoded }}`\n**Triggered by:** ${{ github.triggering_actor }}\n**Build log:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n" - name: Notify on Wire if failed if: failure() uses: 8398a7/action-slack@v3 diff --git a/.github/workflows/_reusable_app_release_without_changelog.yml b/.github/workflows/_reusable_app_release_without_changelog.yml index 26f2ae88540..ecb9535c8e0 100644 --- a/.github/workflows/_reusable_app_release_without_changelog.yml +++ b/.github/workflows/_reusable_app_release_without_changelog.yml @@ -7,6 +7,8 @@ on: is_cloud_build: type: boolean default: true + countly_enabled: + type: boolean datadog_enabled: type: boolean default: false @@ -89,7 +91,7 @@ jobs: WIRE_INTERNAL_GITHUB_USER: ${{ secrets.WIRE_INTERNAL_GITHUB_USER }} WIRE_INTERNAL_GITHUB_TOKEN: ${{ secrets.WIRE_INTERNAL_GITHUB_TOKEN }} APPCENTER_API_TOKEN: ${{ secrets.APPCENTER_API_TOKEN }} - APPCENTER_OWNER_NAME: ${{ secrets.APPCENTER_OWNER_NAME }} + APPCENTER_OWNER_NAME: "Wire" S3_BUCKET: "z-lohika" AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -113,6 +115,7 @@ jobs: PLAYGROUND_TESTFLIGHT_LINK: ${{ secrets.PLAYGROUND_TESTFLIGHT_LINK }} COUNTLY_PRODUCTION_KEY: ${{ secrets.COUNTLY_PRODUCTION_KEY }} COUNTLY_INTERNAL_KEY: ${{ secrets.COUNTLY_INTERNAL_KEY }} + ENABLE_COUNTLY: ${{ inputs.countly_enabled }} SLACK_WEBHOOK_URL: ${{ secrets.WIRE_IOS_CI_WEBHOOK }} SKIP_SECURITY_TESTS: ${{ inputs.skip_security_tests }} SEND_TO_EXTERNALS: ${{ inputs.distribute_externals }} @@ -150,7 +153,7 @@ jobs: uses: actions/cache@v3 with: path: ${{ env.PACKAGES_DIR }} - key: ${{ runner.os }}-swiftpm-project-${{ hashFiles('**/*.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}-${{ env.ENABLE_DATADOG }} + key: ${{ runner.os }}-swiftpm-project-${{ hashFiles('**/*.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}-${{ env.ENABLE_COUNTLY }}-${{ env.ENABLE_DATADOG }} - name: Resolve Swift Package Dependencies run: | ( cd $REPO_ROOT && xcodebuild -resolvePackageDependencies -disableAutomaticPackageResolution -clonedSourcePackagesDirPath "$PACKAGES_DIR" ) diff --git a/.github/workflows/_reusable_run_tests.yml b/.github/workflows/_reusable_run_tests.yml index ffdf87b40a7..a792cb0d15c 100644 --- a/.github/workflows/_reusable_run_tests.yml +++ b/.github/workflows/_reusable_run_tests.yml @@ -1,110 +1,19 @@ on: workflow_call: inputs: - wire-ios: - type: boolean - default: false - required: false - wire-ui: - type: boolean - default: false - required: false - wire-ios-sync-engine: - type: boolean - default: false - required: false - wire-ios-data-model: - type: boolean - default: false - required: false - wire-ios-system: - type: boolean - default: false - required: false - wire-system: - type: boolean - default: false - required: false - wire-ios-request-strategy: - type: boolean - default: false - required: false - wire-api: - type: boolean - default: false - required: false - wire-analytics: - type: boolean - default: false - required: false - wire-datadog: - type: boolean - default: false - required: false - wire-domain-project: - type: boolean - default: false - required: false - wire-domain: - type: boolean - default: false - required: false - wire-ios-transport: - type: boolean - default: false - required: false - wire-ios-share-engine: - type: boolean - default: false - required: false - wire-ios-cryptobox: - type: boolean - default: false - required: false - wire-ios-mocktransport: - type: boolean - default: false - required: false - wire-ios-notification-engine: - type: boolean - default: false - required: false - wire-ios-protos: - type: boolean - default: false - required: false - wire-ios-images: - type: boolean - default: false - required: false - wire-ios-link-preview: - type: boolean - default: false - required: false - wire-ios-utilities: - type: boolean - default: false - required: false - wire-utilities: - type: boolean - default: false - required: false - wire-ios-testing: - type: boolean - default: false - required: false - wire-foundation: - type: boolean - default: false - required: false - scripts: - type: boolean - default: false - required: false + folders: + type: string all: type: boolean default: false required: false + branch: + type: string + required: true + notify_secret: + type: string + required: true + secrets: ZENKINS_USERNAME: required: true @@ -119,17 +28,26 @@ env: # https://docs.fastlane.tools/getting-started/ios/setup/ jobs: run-tests: + name: Run tests runs-on: ghcr.io/cirruslabs/macos-runner:sonoma + outputs: + xcresult_files: ${{ steps.find-xcresults.outputs.xcresult_files }} env: GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_USERNAME: ${{ secrets.ZENKINS_USERNAME }} - SLACK_WEBHOOK_URL: ${{ secrets.WIRE_IOS_CI_WEBHOOK }} + SLACK_WEBHOOK_URL: ${{ secrets[inputs.notify_secret] }} steps: - uses: actions/checkout@v4 with: + ref: ${{ inputs.branch }} lfs: 'true' + - name: Sanitize branch name + run: | + SANITIZED_BRANCH=$(echo "${{ inputs.branch }}" | sed 's/[^a-zA-Z0-9._-]/_/g') + echo "SANITIZED_BRANCH=$SANITIZED_BRANCH" >> $GITHUB_ENV + - name: Retrieve Xcode version run: | echo "XCODE_VERSION=$(cat .xcode-version)" >> $GITHUB_OUTPUT @@ -151,13 +69,6 @@ jobs: path: Carthage key: ${{ runner.os }}-xcode${{ steps.xcode-version.outputs.XCODE_VERSION }}-carthage-${{ hashFiles('Cartfile.resolved') }} - - name: Restore Danger results - id: restore-danger-results - uses: actions/cache/restore@v4 - with: - path: Danger.results - key: ${{ github.event.before || github.event.pull_request.base.sha }}-Danger.results - - name: Bootstrap Carthage if no cache if: steps.cache-carthage.outputs.cache-hit != 'true' run: ./scripts/carthage.sh bootstrap --platform ios --use-xcframeworks @@ -168,7 +79,7 @@ jobs: uses: actions/cache@v3 with: path: ${{ env.PACKAGES_DIR }} - key: ${{ runner.os }}-swiftpm-project-${{ hashFiles('**/*.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} + key: ${{ runner.os }}-swiftpm-project-${{ hashFiles('**/*.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}-false # Resolve Swift Package Dependencies - name: Resolve Swift Package Dependencies @@ -180,6 +91,11 @@ jobs: with: bundler-cache: true + - name: Display all fastlane lanes + if: ${{ contains(inputs.folders, 'fastlane') || inputs.all }} + run: | + bundle exec fastlane lanes + - name: Setup workspace run: | ./setup.sh @@ -188,542 +104,53 @@ jobs: run: | bundle exec fastlane prepare_for_tests - # Scripts - - - name: Test Scripts - if: ${{ inputs.scripts || inputs.all }} - run: | - echo "Scripts has changes" - echo "Testing Scripts..." - xcrun swift test --package-path ./scripts - - # WireFoundation - - - name: Build WireFoundation - if: ${{ inputs.wire-foundation || inputs.all }} + - name: "Test all selected frameworks" + id: set_frameworks run: | - echo "WireFoundation has changes" - echo "Building WireFoundation..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireFoundation-Package -resultBundlePath xcodebuild-wire-foundation.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-foundation.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-foundation || inputs.all }} - with: - results: xcodebuild-wire-foundation.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + bundle exec fastlane test_frameworks folders:${{ inputs.folders }} all:${{ inputs.all }} - - name: Test WireFoundation - if: ${{ inputs.wire-foundation || inputs.all }} + - name: Find all .xcresult files + if: ${{ always() && github.event_name == 'pull_request' }} + id: find-xcresults run: | - echo "Testing WireFoundation..." - xcodebuild test -workspace wire-ios-mono.xcworkspace -scheme WireFoundation-Package -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-foundation.log | bundle exec xcpretty --report junit --output build/reports/WireFoundation.junit - exit ${PIPESTATUS[0]} - - # WireSystem Project - - - name: Build WireSystem Project - if: ${{ inputs.wire-ios-system || inputs.all }} + # Find all .xcresult directories and convert them to a JSON array + files=`find artifacts -type d -name "*.xcresult" | jq -R . | jq -s .` + echo xcresult_files=${files} >> $GITHUB_OUTPUT + - name: Debug Print xcresult files run: | - echo "WireSystem Project has changes" - echo "Building WireSystem Project..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireSystem -resultBundlePath xcodebuild-wire-ios-system.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-system.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-system || inputs.all }} - with: - results: xcodebuild-wire-ios-system.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireSystem Project - if: ${{ inputs.wire-ios-system || inputs.all }} - run: | - echo "Testing WireSystem Project..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireSystem -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-system.log | bundle exec xcpretty --report junit --output build/reports/WireSystemProject.junit - exit ${PIPESTATUS[0]} - - # WireTesting Project - - - name: Build WireTesting Project - if: ${{ inputs.wire-ios-testing || inputs.all }} - run: | - echo "WireTesting Project has changes" - echo "Building WireTesting Project..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireTesting -resultBundlePath xcodebuild-wire-ios-testing.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-testing.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-testing || inputs.all }} - with: - results: xcodebuild-wire-ios-testing.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireTesting Project - if: ${{ inputs.wire-ios-testing || inputs.all }} - run: | - echo "Testing WireTesting Project..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireTesting -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-testing.log | bundle exec xcpretty --report junit --output build/reports/WireTestingProject.junit - exit ${PIPESTATUS[0]} - - # WireUtilities Project - - - name: Build WireUtilities Project - if: ${{ inputs.wire-ios-utilities || inputs.all }} - run: | - echo "WireUtilities Project has changes" - echo "Building WireUtilities Project..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireUtilities -resultBundlePath xcodebuild-wire-ios-utilities.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-utilities.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-utilities || inputs.all }} - with: - results: xcodebuild-wire-ios-utilities.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireUtilities Project - if: ${{ inputs.wire-ios-utilities || inputs.all }} - run: | - echo "Testing WireUtilities Project..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireUtilities -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-utilities.log | bundle exec xcpretty --report junit --output build/reports/WireUtilitiesProject.junit - exit ${PIPESTATUS[0]} - - # WireCryptobox - - - name: Build WireCryptobox - if: ${{ inputs.wire-ios-cryptobox || inputs.all }} - run: | - echo "WireCryptobox has changes" - echo "Building WireCryptobox..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireCryptobox -resultBundlePath xcodebuild-wire-ios-cryptobox.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-cryptobox.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-cryptobox || inputs.all }} - with: - results: xcodebuild-wire-ios-cryptobox.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireCryptobox - if: ${{ inputs.wire-ios-cryptobox || inputs.all }} - run: | - echo "Testing WireCryptobox..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireCryptobox -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-cryptobox.log | bundle exec xcpretty --report junit --output build/reports/WireCryptobox.junit - exit ${PIPESTATUS[0]} - - # WireTransport - - - name: Build WireTransport - if: ${{ inputs.wire-ios-transport || inputs.all }} - run: | - echo "WireTransport has changes" - echo "Building WireTransport..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireTransport -resultBundlePath xcodebuild-wire-ios-transport.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-transport.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-transport || inputs.all }} - with: - results: xcodebuild-wire-ios-transport.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireTransport - if: ${{ inputs.wire-ios-transport || inputs.all }} - run: | - echo "Testing WireTransport..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireTransport -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-transport.log | bundle exec xcpretty --report junit --output build/reports/WireTransport.junit - exit ${PIPESTATUS[0]} - - # WireLinkPreview - - - name: Build WireLinkPreview - if: ${{ inputs.wire-ios-link-preview || inputs.all }} - run: | - echo "WireLinkPreview has changes" - echo "Building WireLinkPreview..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireLinkPreview -resultBundlePath xcodebuild-wire-ios-link-preview.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-link-preview.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-link-preview || inputs.all }} - with: - results: xcodebuild-wire-ios-link-preview.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireLinkPreview - if: ${{ inputs.wire-ios-link-preview || inputs.all }} - run: | - echo "Testing WireLinkPreview..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireLinkPreview -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-link-preview.log | bundle exec xcpretty --report junit --output build/reports/WireLinkPreview.junit - exit ${PIPESTATUS[0]} - - # WireImages - - - name: Build WireImages - if: ${{ inputs.wire-ios-images || inputs.all }} - run: | - echo "WireImages has changes" - echo "Building WireImages..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireImages -resultBundlePath xcodebuild-wire-ios-images.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-images.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-images || inputs.all }} - with: - results: xcodebuild-wire-ios-images.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireImages - if: ${{ inputs.wire-ios-images || inputs.all }} - run: | - echo "Testing WireImages..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireImages -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-images.log | bundle exec xcpretty --report junit --output build/reports/WireImages.junit - exit ${PIPESTATUS[0]} - - # WireProtos - - - name: Build WireProtos - if: ${{ inputs.wire-ios-protos || inputs.all }} - run: | - echo "WireProtos has changes" - echo "Building WireProtos..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireProtos -resultBundlePath xcodebuild-wire-ios-protos.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-protos.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-protos || inputs.all }} - with: - results: xcodebuild-wire-ios-protos.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireProtos - if: ${{ inputs.wire-ios-protos || inputs.all }} - run: | - echo "Testing WireProtos..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireProtos -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-protos.log | bundle exec xcpretty - exit ${PIPESTATUS[0]} - - # WireMockTransport - - - name: Build WireMockTransport - if: ${{ inputs.wire-ios-mocktransport || inputs.all }} - run: | - echo "WireMockTransport has changes" - echo "Building WireMockTransport..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireMockTransport -resultBundlePath xcodebuild-wire-ios-mocktransport.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-mocktransport.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-mocktransport || inputs.all }} - with: - results: xcodebuild-wire-ios-mocktransport.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireMockTransport - if: ${{ inputs.wire-ios-mocktransport || inputs.all }} - run: | - echo "Testing WireMockTransport..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireMockTransport -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-mocktransport.log | bundle exec xcpretty --report junit --output build/reports/WireMockTransport.junit - exit ${PIPESTATUS[0]} - - # WireDataModel - - - name: Build WireDataModel - if: ${{ inputs.wire-ios-data-model || inputs.all }} - run: | - echo "WireDataModel has changes" - echo "Building WireDataModel..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireDataModel -resultBundlePath xcodebuild-wire-ios-data-model.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-data-model.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-data-model || inputs.all }} - with: - results: xcodebuild-wire-ios-data-model.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireDataModel - if: ${{ inputs.wire-ios-data-model || inputs.all }} - run: | - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireDataModel -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-data-model.log | bundle exec xcpretty --report junit --output build/reports/WireDataModel.junit - exit ${PIPESTATUS[0]} - - # WireAPI - - - name: Build WireAPI - if: ${{ inputs.wire-api || inputs.all }} - run: | - echo "WireAPI has changes" - echo "Building WireAPI..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireAPI -resultBundlePath xcodebuild-wire-api.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-api.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-api || inputs.all }} - with: - results: xcodebuild-wire-api.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireAPI - if: ${{ inputs.wire-api || inputs.all }} - run: | - echo "Testing WireAPI..." - xcodebuild test -workspace wire-ios-mono.xcworkspace -scheme WireAPI -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-api.log | bundle exec xcpretty --report junit --output build/reports/WireAPI.junit - exit ${PIPESTATUS[0]} - - # WireDatadog - - - name: Build WireDatadog - if: ${{ inputs.wire-datadog || inputs.all }} - run: | - echo "WireDatadog has changes" - echo "Building WireDatadog..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireDatadog -resultBundlePath xcodebuild-wire-datadog.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-datadog.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-datadog || inputs.all }} - with: - results: xcodebuild-wire-datadog.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # No tests exist for WireDatadog currently - - # WireAnalytics - - - name: Build WireAnalytics - if: ${{ inputs.wire-analytics || inputs.all }} - run: | - echo "WireAnalytics has changes" - echo "Building WireAnalytics..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireAnalytics -resultBundlePath xcodebuild-wire-analytics.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-analytics.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-analytics || inputs.all }} - with: - results: xcodebuild-wire-analytics.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # No tests exist for WireAnalytics currently - - # WireDomain - - - name: Build WireDomain - if: ${{ inputs.wire-domain || inputs.all }} - run: | - echo "WireDomain has changes" - echo "Building WireDomain..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireDomainPackage -resultBundlePath xcodebuild-wire-domain.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-domain.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-domain || inputs.all }} - with: - results: xcodebuild-wire-domain.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireDomain - if: ${{ inputs.wire-domain || inputs.all }} - run: | - echo "Testing WireDomain..." - xcodebuild test -workspace wire-ios-mono.xcworkspace -scheme WireDomainPackage -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-domain.log | bundle exec xcpretty --report junit --output build/reports/WireDomain.junit - exit ${PIPESTATUS[0]} - - # WireDomain Project - - - name: Build WireDomain Project - if: ${{ inputs.wire-domain-project || inputs.all }} - run: | - echo "WireDomain Project has changes" - echo "Building WireDomain Project..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireDomain -resultBundlePath xcodebuild-wire-domain-project.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-domain-project.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-domain-project || inputs.all }} - with: - results: xcodebuild-wire-domain-project.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireDomain Project - if: ${{ inputs.wire-domain-project || inputs.all }} - run: | - echo "Testing WireDomain Project..." - xcodebuild test -workspace wire-ios-mono.xcworkspace -scheme WireDomain -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-domain-project.log | bundle exec xcpretty --report junit --output build/reports/WireDomainProject.junit - exit ${PIPESTATUS[0]} - - # WireRequestStrategy - - - name: Build WireRequestStrategy - if: ${{ inputs.wire-ios-request-strategy || inputs.all }} - run: | - echo "WireRequestStrategy has changes" - echo "Building WireRequestStrategy..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireRequestStrategy -resultBundlePath xcodebuild-wire-ios-request-strategy.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-request-strategy.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-request-strategy || inputs.all }} - with: - results: xcodebuild-wire-ios-request-strategy.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireRequestStrategy - if: ${{ inputs.wire-ios-request-strategy || inputs.all }} - run: | - echo "Testing WireRequestStrategy..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireRequestStrategy -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-request-strategy.log | bundle exec xcpretty --report junit --output build/reports/WireRequestStrategy.junit - exit ${PIPESTATUS[0]} - - # WireShareEngine - - - name: Build WireShareEngine - if: ${{ inputs.wire-ios-share-engine || inputs.all }} - run: | - echo "WireShareEngine has changes" - echo "Building WireShareEngine..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireShareEngine -resultBundlePath xcodebuild-wire-ios-share-engine.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-share-engine.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-share-engine || inputs.all }} - with: - results: xcodebuild-wire-ios-share-engine.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireShareEngine - if: ${{ inputs.wire-ios-share-engine || inputs.all }} - run: | - echo "Testing WireShareEngine..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireShareEngine -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-share-engine.log | bundle exec xcpretty --report junit --output build/reports/WireShareEngine.junit - exit ${PIPESTATUS[0]} - - # WireSyncEngine - - - name: Build WireSyncEngine - if: ${{ inputs.wire-ios-sync-engine || inputs.all }} - run: | - echo "WireSyncEngine has changes" - echo "Building WireSyncEngine..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireSyncEngine -resultBundlePath xcodebuild-wire-ios-sync-engine.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-sync-engine.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-sync-engine || inputs.all }} - with: - results: xcodebuild-wire-ios-sync-engine.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireSyncEngine - if: ${{ inputs.wire-ios-sync-engine || inputs.all }} - run: | - echo "Testing WireSyncEngine..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireSyncEngine -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-sync-engine.log | bundle exec xcpretty --report junit --output build/reports/WireSyncEngine.junit - exit ${PIPESTATUS[0]} - - # WireUI - - - name: Test WireUI - if: ${{ inputs.wire-ui || inputs.all }} - run: | - echo "WireUI has changes" - echo "Building WireUI..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireUI-Package -resultBundlePath xcodebuild-wire-ui.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ui.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ui || inputs.all }} - with: - results: xcodebuild-wire-ui.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireUI - if: ${{ inputs.wire-ui || inputs.all }} - run: | - echo "Testing WireUI..." - xcodebuild test -workspace wire-ios-mono.xcworkspace -scheme WireUI-Package -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ui.log | bundle exec xcpretty --report junit --output build/reports/WireUI.junit - exit ${PIPESTATUS[0]} - - # Wire-iOS - - - name: Build Wire-iOS - if: ${{ inputs.wire-ios || inputs.all }} - run: | - echo "Wire-iOS has changes" - echo "Building Wire-iOS..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme Wire-iOS -resultBundlePath xcodebuild-wire-ios.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios || inputs.all }} + echo "xcresult files: ${{ steps.find-xcresults.outputs.xcresult_files }}" + - name: Upload .xcresult files as artifact + if: always() + uses: actions/upload-artifact@v4 with: - results: xcodebuild-wire-ios.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: XCResults for ${{ env.SANITIZED_BRANCH }} (${{ github.run_id }} - ${{ github.run_attempt}}) + path: artifacts/**/*.xcresult # Adjust this if necessary - - name: Test Wire-iOS - if: ${{ inputs.wire-ios || inputs.all }} - run: | - echo "Testing Wire-iOS..." - xcodebuild test -workspace wire-ios-mono.xcworkspace -scheme Wire-iOS -testPlan AllTests -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios.log | bundle exec xcpretty --report junit --output build/reports/Wire-iOS-EN.junit - exit ${PIPESTATUS[0]} - - - name: Test Wire-iOS German Locale Tests - if: ${{ inputs.wire-ios || inputs.all }} + # Scripts + - name: Test Scripts + if: ${{ contains(inputs.folders, 'scripts') || inputs.all }} run: | - echo "Testing Wire-iOS German Locale Tests..." - xcodebuild test -workspace wire-ios-mono.xcworkspace -scheme Wire-iOS -testPlan GermanLocaleTests -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-de.log | bundle exec xcpretty --report junit --output build/reports/Wire-iOS-DE.junit - exit ${PIPESTATUS[0]} + echo "Scripts has changes" + echo "Testing Scripts..." + xcrun swift test --package-path ./scripts - name: Upload Failed snapshots uses: actions/upload-artifact@v4 if: always() with: - name: Failed Snapshots and log + name: Failed Snapshots and log for ${{ env.SANITIZED_BRANCH }} (${{ github.run_id }} - ${{ github.run_attempt}}) path: | **/SnapshotResults/ xcodebuild*.log - # WORKAROUND: if we test WireNotificationEngine and then Wire-iOS, we'll get an error when trying to build - # Wire-iOS stating that symbols from the notification can't be found. to workaround this, test the notification - # after the Wire-iOS. - - name: Build WireNotificationEngine - if: ${{ inputs.wire-ios-notification-engine || inputs.all }} - run: | - echo "WireNotificationEngine has changes" - echo "Building WireNotificationEngine..." - xcodebuild build-for-testing -workspace wire-ios-mono.xcworkspace -scheme WireNotificationEngine -resultBundlePath xcodebuild-wire-ios-notification-engine.xcresult -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee xcodebuild-wire-ios-notification-engine.log | bundle exec xcpretty - - - uses: wireapp/analyze-xcoderesults-action@v1 - if: ${{ inputs.wire-ios-notification-engine || inputs.all }} - with: - results: xcodebuild-wire-ios-notification-engine.xcresult - warningAnnotations: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Test WireNotificationEngine - if: ${{ inputs.wire-ios-notification-engine || inputs.all }} - run: | - echo "Testing WireNotificationEngine..." - xcodebuild test -retry-tests-on-failure -workspace wire-ios-mono.xcworkspace -scheme WireNotificationEngine -destination 'platform=iOS Simulator,OS=${{ env.IOS_VERSION }},name=${{ env.IPHONE_MODEL }}' | tee -a xcodebuild-wire-ios-notification-engine.log | bundle exec xcpretty --report junit --output build/reports/WireNotificationEngine.junit - exit ${PIPESTATUS[0]} - - - name: Save cache with warning counts - # This step should only run for the test_develop.yml workflow - if: ${{ !cancelled() && github.event.action == 'push' }} - uses: actions/cache/save@v4 - with: - path: Danger.results - key: ${{ github.event.head_commit.id || github.event.pull_request.merge_commit_sha || github.event.pull_request.head.sha }}-Danger.results - - name: Upload Test Reports as Artifacts uses: actions/upload-artifact@v4 if: always() with: - name: test-reports + name: Test reports for ${{ env.SANITIZED_BRANCH }} (${{ github.run_id }} - ${{ github.run_attempt}}) path: | build/reports/*.junit + artifacts/**/*.junit - name: Prepare visual representation of test results uses: EnricoMi/publish-unit-test-result-action/macos@v2 @@ -731,34 +158,94 @@ jobs: with: files: | build/reports/*.junit + artifacts/**/*.junit compare_to_earlier_commit: false - name: Archiving DerivedData Logs uses: actions/upload-artifact@v4 if: always() with: - name: derived-data-xcode + name: Derived Data Xcode for ${{env.SANITIZED_BRANCH }} (${{ github.run_id }} - ${{ github.run_attempt}}) path: | /Users/runner/Library/Developer/Xcode/DerivedData/**/Logs/** ~/Library/Logs/DiagnosticReports/** - - name: Notify on Wire if failed - if: ${{ failure() && github.ref_name == 'develop' }} + - name: Get Commit SHA + if: always() + id: get_commit_sha + run: echo "commit_sha=$(git rev-parse HEAD)" >> $GITHUB_ENV + + - name: Notify on Wire for iOS Nightly 🤖 + uses: 8398a7/action-slack@v3 + if: ${{ inputs.all && always() }} + with: + status: ${{ job.status }} + text: | + **Tests for ${{ inputs.branch }}** + + **Commit:** [${{ env.commit_sha }}](https://github.com/wireapp/wire-ios-mono/commit/${{ env.commit_sha }}) + **Triggered by:** ${{ github.triggering_actor }} + **Build log:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Notify on Wire for iOS CI uses: 8398a7/action-slack@v3 + if: ${{ !inputs.all && failure() }} with: status: ${{ job.status }} - text: "🆘 Tests for 'develop' failed 🆘\ncommit: https://github.com/wireapp/wire-ios-mono/commit/${{ github.sha }}\n**Triggered by:** ${{ github.triggering_actor }}\n**Build log:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n" + text: | + **🚨 Tests for ${{ inputs.branch }} failed 🆘** + + **Commit:** [${{ env.commit_sha }}](https://github.com/wireapp/wire-ios-mono/commit/${{ env.commit_sha }}) + **Triggered by:** ${{ github.triggering_actor }} + **Build log:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + analyze-xcresults: + needs: run-tests + name: Analyze xcresults + if: ${{ always() && github.event_name == 'pull_request' }} + runs-on: ghcr.io/cirruslabs/macos-runner:sonoma + strategy: + matrix: + xcresult_file: ${{ fromJson(needs.run-tests.outputs.xcresult_files) }} + fail-fast: false # avoid to fail one job + steps: + - name: Sanitize branch name + run: | + SANITIZED_BRANCH=$(echo "${{ inputs.branch }}" | sed 's/[^a-zA-Z0-9._-]/_/g') + echo "SANITIZED_BRANCH=$SANITIZED_BRANCH" >> $GITHUB_ENV + + - name: Download .xcresult files + uses: actions/download-artifact@v4 + with: + name: XCResults for ${{ env.SANITIZED_BRANCH }} (${{ github.run_id }} - ${{ github.run_attempt}}) + path: ./artifacts + + - name: Display downloaded files + run: ls -R + + - name: Analyze .xcresult file + uses: wireapp/analyze-xcoderesults-action@v1 + with: + results: ${{ matrix.xcresult_file }} + warningAnnotations: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} upload-test-results-datadadog: + name: Upload to Datadog runs-on: ubuntu-latest needs: run-tests if: always() steps: + - name: Sanitize branch name + run: | + SANITIZED_BRANCH=$(echo "${{ inputs.branch }}" | sed 's/[^a-zA-Z0-9._-]/_/g') + echo "SANITIZED_BRANCH=$SANITIZED_BRANCH" >> $GITHUB_ENV + - name: Download tests results - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 continue-on-error: true with: - name: test-reports + name: Test reports for ${{ env.SANITIZED_BRANCH }} (${{ github.run_id }} - ${{ github.run_attempt}}) - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/.github/workflows/beta_app_release.yml b/.github/workflows/beta_app_release.yml index 69ea1131bfc..427d1eefafe 100644 --- a/.github/workflows/beta_app_release.yml +++ b/.github/workflows/beta_app_release.yml @@ -14,6 +14,7 @@ jobs: testflight_beta: uses: ./.github/workflows/_reusable_app_release.yml with: + countly_enabled: true datadog_enabled: true fastlane_action: testflight_beta distribute_externals: ${{ inputs.distribute_externals }} diff --git a/.github/workflows/build_pr.yml b/.github/workflows/build_pr.yml index d960fee4cfc..70cfae282b7 100644 --- a/.github/workflows/build_pr.yml +++ b/.github/workflows/build_pr.yml @@ -24,6 +24,7 @@ jobs: ) && github.event.pull_request.draft == false uses: ./.github/workflows/_reusable_app_release_without_changelog.yml with: + countly_enabled: false datadog_enabled: false fastlane_action: development_pr is_cloud_build: true diff --git a/.github/workflows/c1_c2_c3_app_release_production.yml b/.github/workflows/c1_c2_c3_app_release_production.yml index 384fa7b99c7..6e5e6d3ce80 100644 --- a/.github/workflows/c1_c2_c3_app_release_production.yml +++ b/.github/workflows/c1_c2_c3_app_release_production.yml @@ -24,6 +24,7 @@ jobs: with: fastlane_action: appstore_col_1_prod is_cloud_build: false + countly_enabled: false skip_security_tests: false secrets: inherit @@ -34,6 +35,7 @@ jobs: with: fastlane_action: appstore_col_2_prod is_cloud_build: false + countly_enabled: false skip_security_tests: false secrets: inherit @@ -43,5 +45,6 @@ jobs: with: fastlane_action: appstore_col_3_prod is_cloud_build: false + countly_enabled: false skip_security_tests: false secrets: inherit diff --git a/.github/workflows/c1_c2_c3_app_release_restricted.yml b/.github/workflows/c1_c2_c3_app_release_restricted.yml index 25ddaa56b3e..ca724a985a0 100644 --- a/.github/workflows/c1_c2_c3_app_release_restricted.yml +++ b/.github/workflows/c1_c2_c3_app_release_restricted.yml @@ -31,6 +31,7 @@ jobs: with: fastlane_action: appstore_col_1_restricted is_cloud_build: false + countly_enabled: false datadog_enabled: ${{ inputs.datadog_enabled }} skip_security_tests: ${{ inputs.skip_security_tests }} secrets: inherit @@ -41,6 +42,7 @@ jobs: with: fastlane_action: appstore_col_2_restricted is_cloud_build: false + countly_enabled: false datadog_enabled: ${{ inputs.datadog_enabled }} skip_security_tests: ${{ inputs.skip_security_tests }} secrets: inherit @@ -51,6 +53,7 @@ jobs: with: fastlane_action: appstore_col_3_restricted is_cloud_build: false + countly_enabled: false datadog_enabled: ${{ inputs.datadog_enabled }} skip_security_tests: ${{ inputs.skip_security_tests }} secrets: inherit diff --git a/.github/workflows/c1_c3_app_release_production.yml b/.github/workflows/c1_c3_app_release_production.yml index cab902bd6b1..084e1a15a65 100644 --- a/.github/workflows/c1_c3_app_release_production.yml +++ b/.github/workflows/c1_c3_app_release_production.yml @@ -11,6 +11,7 @@ jobs: with: fastlane_action: appstore_col_1_prod is_cloud_build: false + countly_enabled: false skip_security_tests: false secrets: inherit @@ -19,5 +20,6 @@ jobs: with: fastlane_action: appstore_col_3_prod is_cloud_build: false + countly_enabled: false skip_security_tests: false secrets: inherit diff --git a/.github/workflows/c1_c3_app_release_restricted.yml b/.github/workflows/c1_c3_app_release_restricted.yml index 6859e68a449..98c29ba3da5 100644 --- a/.github/workflows/c1_c3_app_release_restricted.yml +++ b/.github/workflows/c1_c3_app_release_restricted.yml @@ -10,6 +10,7 @@ jobs: uses: ./.github/workflows/_reusable_app_release.yml with: fastlane_action: appstore_col_1_restricted + countly_enabled: false skip_security_tests: false secrets: inherit @@ -17,5 +18,6 @@ jobs: uses: ./.github/workflows/_reusable_app_release.yml with: fastlane_action: appstore_col_3_restricted + countly_enabled: false skip_security_tests: false secrets: inherit diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index a12cf155be9..e31ad0203e3 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -13,6 +13,7 @@ jobs: development: uses: ./.github/workflows/_reusable_app_release.yml with: + countly_enabled: true datadog_enabled: true fastlane_action: development is_cloud_build: true diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index a661d6ecade..c736c0f2bd8 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -15,6 +15,7 @@ jobs: playground: uses: ./.github/workflows/_reusable_app_release.yml with: + countly_enabled: true datadog_enabled: true distribute_externals: ${{ inputs.distribute_externals }} fastlane_action: playground diff --git a/.github/workflows/test_develop.yml b/.github/workflows/test_develop.yml index 8dcc66dcc92..bc9ab833c19 100644 --- a/.github/workflows/test_develop.yml +++ b/.github/workflows/test_develop.yml @@ -1,19 +1,24 @@ -name: Test Develop +name: Nightly Tests on: workflow_dispatch: - push: - branches: - - 'develop' - + schedule: + - cron: "0 21 * * MON-FRI" + # This is what will cancel the workflow concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - trigger_tests_develop: + trigger_tests: + strategy: + matrix: + branch: [develop, release/cycle-3.114] # List other branches here + fail-fast: false # avoid to fail one job uses: ./.github/workflows/_reusable_run_tests.yml with: all: true + branch: ${{ matrix.branch }} + notify_secret: WIRE_IOS_NIGHTLY_WEBHOOK secrets: inherit diff --git a/.github/workflows/test_pr_changes.yml b/.github/workflows/test_pr_changes.yml index 477b7413729..3a321e4532b 100644 --- a/.github/workflows/test_pr_changes.yml +++ b/.github/workflows/test_pr_changes.yml @@ -15,6 +15,7 @@ jobs: # This job will tell us which frameworks have source code changes. # We'll use the results to run tests only for changed frameworks. detect-changes: + name: Detect PR Changes if: > github.event.action != 'edited' || ( github.event.changes.title == null && @@ -26,42 +27,23 @@ jobs: runs-on: ubuntu-latest # Set job outputs to values from filter step outputs: - wire-ios: ${{ steps.filter.outputs.wire-ios == 'true' || steps.filter.outputs.carthage == 'true' }} - wire-ui: ${{ steps.filter.outputs.wire-ui }} - wire-ios-sync-engine: ${{ steps.filter.outputs.wire-ios-sync-engine }} - wire-ios-data-model: ${{ steps.filter.outputs.wire-ios-data-model }} - wire-ios-system: ${{ steps.filter.outputs.wire-ios-system }} - wire-ios-request-strategy: ${{ steps.filter.outputs.wire-ios-request-strategy }} - wire-api: ${{ steps.filter.outputs.wire-api }} - wire-analytics: ${{ steps.filter.outputs.wire-analytics }} - wire-datadog: ${{ steps.filter.outputs.wire-datadog }} - wire-domain-project: ${{ steps.filter.outputs.wire-domain-project }} - wire-domain: ${{ steps.filter.outputs.wire-domain }} - wire-ios-transport: ${{ steps.filter.outputs.wire-ios-transport }} - wire-ios-share-engine: ${{ steps.filter.outputs.wire-ios-share-engine }} - wire-ios-cryptobox: ${{ steps.filter.outputs.wire-ios-cryptobox }} - wire-ios-mocktransport: ${{ steps.filter.outputs.wire-ios-mocktransport }} - wire-ios-notification-engine: ${{ steps.filter.outputs.wire-ios-notification-engine }} - wire-ios-protos: ${{ steps.filter.outputs.wire-ios-protos }} - wire-ios-images: ${{ steps.filter.outputs.wire-ios-images }} - wire-ios-link-preview : ${{ steps.filter.outputs.wire-ios-link-preview }} - wire-ios-utilities: ${{ steps.filter.outputs.wire-ios-utilities }} - wire-ios-testing: ${{ steps.filter.outputs.wire-ios-testing }} - wire-foundation: ${{ steps.filter.outputs.wire-foundation }} - scripts: ${{ steps.filter.outputs.scripts }} + folders: ${{ steps.filter.outputs.changes }} steps: - uses: actions/checkout@v4 with: - lfs: 'true' + lfs: 'false' # as long as we use lfs only for snapshotTesting it should not be useful here - uses: dorny/paths-filter@v2 id: filter with: filters: | + fastlane: + - 'fastlane/**' wire-ios: - 'wire-ios/**' - wire-ui: + WireUI: - 'WireUI/**' + - 'wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireUIAll.xcscheme' wire-ios-share-engine: - 'wire-ios-share-engine/**' wire-ios-notification-engine: @@ -70,20 +52,17 @@ jobs: - 'wire-ios-sync-engine/**' wire-ios-request-strategy: - 'wire-ios-request-strategy/**' - wire-api: + WireAPI: - 'WireAPI/**' - wire-analytics: - - 'WireAnalytics/Sources/WireAnalytics/**' - wire-datadog: - - 'WireAnalytics/Sources/WireDatadog/**' - wire-domain-project: - - 'WireDomain/Project/**' - - 'WireDomain/Sources/WireDomain/**' - - 'WireDomain/Sources/WireDomainSupport/**' - wire-domain: - - 'WireDomain/*.*' - - 'WireDomain/Sources/Package/**' - - 'WireDomain/Sources/PackageSupport/**' + - 'wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireAPIAll.xcscheme' + WireAnalytics: + - 'WireAnalytics/**' + - 'wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireAnalyticsAll.xcscheme' + WireDomain: + - 'WireDomain/**' + WireLogging: + - 'WireLogging/**' + - 'wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireLoggingAll.xcscheme' wire-ios-data-model: - 'wire-ios-data-model/**' wire-ios-transport: @@ -102,8 +81,9 @@ jobs: - 'wire-ios-utilities/**' wire-ios-testing: - 'wire-ios-testing/**' - wire-foundation: + WireFoundation: - 'WireFoundation/**' + - 'wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireFoundationAll.xcscheme' wire-ios-system: - 'wire-ios-system/**' scripts: @@ -113,29 +93,10 @@ jobs: trigger_tests_pr: needs: detect-changes + name: PR Tests uses: ./.github/workflows/_reusable_run_tests.yml with: - wire-ios: ${{ needs.detect-changes.outputs.wire-ios == 'true' }} - wire-ui: ${{ needs.detect-changes.outputs.wire-ui == 'true' }} - wire-ios-sync-engine: ${{ needs.detect-changes.outputs.wire-ios-sync-engine == 'true' }} - wire-ios-data-model: ${{ needs.detect-changes.outputs.wire-ios-data-model == 'true' }} - wire-ios-system: ${{ needs.detect-changes.outputs.wire-ios-system == 'true' }} - wire-ios-request-strategy: ${{ needs.detect-changes.outputs.wire-ios-request-strategy == 'true' }} - wire-api: ${{ needs.detect-changes.outputs.wire-api == 'true' }} - wire-analytics: ${{ needs.detect-changes.outputs.wire-analytics == 'true' }} - wire-datadog: ${{ needs.detect-changes.outputs.wire-datadog == 'true' }} - wire-domain-project: ${{ needs.detect-changes.outputs.wire-domain-project == 'true' }} - wire-domain: ${{ needs.detect-changes.outputs.wire-domain == 'true' }} - wire-ios-transport: ${{ needs.detect-changes.outputs.wire-ios-transport == 'true' }} - wire-ios-share-engine: ${{ needs.detect-changes.outputs.wire-ios-share-engine == 'true' }} - wire-ios-cryptobox: ${{ needs.detect-changes.outputs.wire-ios-cryptobox == 'true' }} - wire-ios-mocktransport: ${{ needs.detect-changes.outputs.wire-ios-mocktransport == 'true' }} - wire-ios-notification-engine: ${{ needs.detect-changes.outputs.wire-ios-notification-engine == 'true' }} - wire-ios-protos: ${{ needs.detect-changes.outputs.wire-ios-protos == 'true' }} - wire-ios-images: ${{ needs.detect-changes.outputs.wire-ios-images == 'true' }} - wire-ios-link-preview: ${{ needs.detect-changes.outputs.wire-ios-link-preview == 'true' }} - wire-ios-utilities: ${{ needs.detect-changes.outputs.wire-ios-utilities == 'true' }} - wire-ios-testing: ${{ needs.detect-changes.outputs.wire-ios-testing == 'true' }} - wire-foundation: ${{ needs.detect-changes.outputs.wire-foundation == 'true' }} - scripts: ${{ needs.detect-changes.outputs.scripts == 'true' }} + folders: ${{ join(fromJson(needs.detect-changes.outputs.folders), ',') }} + branch: ${{ github.head_ref }} + notify_secret: WIRE_IOS_CI_WEBHOOK secrets: inherit diff --git a/.gitignore b/.gitignore index 502fca9230f..312e39287c6 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,10 @@ playground.xcworkspace # .swiftpm .build/ +# Ignore Package.resolved files in packages and projects, only track the one in the workspace. +**/Package.resolved +!wire-ios-mono.xcworkspace/xcshareddata/swiftpm/Package.resolved +**/*-Package.xcscheme # CocoaPods # @@ -97,3 +101,4 @@ vendor/* # Failing Snapshot Tests SnapshotResults +artifacts/ diff --git a/Cartfile b/Cartfile index b003441554f..d806c954bc5 100644 --- a/Cartfile +++ b/Cartfile @@ -3,5 +3,5 @@ github "wireapp/libsodium" "1.0.14" github "wireapp/ZipArchive" "v2.4.2" github "wireapp/libPhoneNumber-iOS" "1.1" github "wireapp/ocmock" "v3.4.3_Xcode14.3.1" -github "wireapp/core-crypto" "v1.0.2" +github "wireapp/core-crypto" "v3.0.0" binary "wire-avs.json" diff --git a/Cartfile.private b/Cartfile.private index ea0e37bc750..41b3630c60f 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1,2 +1,2 @@ # used by Wire-Proto, this is not compiled but just checked out by Carthage. -github "wireapp/generic-message-proto" "v1.50.0" +github "wireapp/generic-message-proto" "v1.51.0" diff --git a/Cartfile.resolved b/Cartfile.resolved index 44098383289..1991a9fe420 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ -binary "wire-avs.json" "9.9.10" +binary "wire-avs.json" "9.10.16" github "wireapp/ZipArchive" "v2.4.2" -github "wireapp/core-crypto" "v1.0.2" +github "wireapp/core-crypto" "v3.0.0" github "wireapp/cryptobox-ios" "v1.1.0_xcframework_arm64simulator" -github "wireapp/generic-message-proto" "v1.50.0" +github "wireapp/generic-message-proto" "v1.51.0" github "wireapp/libPhoneNumber-iOS" "1.1" github "wireapp/libsodium" "1.0.14" github "wireapp/ocmock" "v3.4.3_Xcode14.3.1" diff --git a/Gemfile b/Gemfile index 726017b0f68..533a32d2214 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -ruby File.read('.ruby-version').strip +ruby file: ".ruby-version" gem 'fastlane' gem 'git' diff --git a/Gemfile.lock b/Gemfile.lock index 9cbd966673a..4e3796df9cd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,16 +10,16 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.1009.0) - aws-sdk-core (3.213.0) + aws-partitions (1.1020.0) + aws-sdk-core (3.214.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.95.0) + aws-sdk-kms (1.96.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.171.0) + aws-sdk-s3 (1.176.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -97,7 +97,7 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.225.0) + fastlane (2.226.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -137,7 +137,7 @@ GEM tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) + xcpretty (~> 0.4.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-appcenter (2.1.2) fastlane-plugin-datadog (0.2.0) @@ -185,7 +185,7 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.7) + http-cookie (1.0.8) domain_name (~> 0.5) httparty (0.22.0) csv @@ -193,11 +193,11 @@ GEM multi_xml (>= 0.5.2) httpclient (2.8.3) jmespath (1.6.2) - json (2.8.2) + json (2.9.0) jwt (2.9.3) base64 - kramdown (2.5.0) - rexml (>= 3.3.6) + kramdown (2.5.1) + rexml (>= 3.3.9) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) mini_magick (4.13.2) @@ -206,7 +206,7 @@ GEM multi_xml (0.7.1) bigdecimal (~> 3.1) multipart-post (2.4.1) - mutex_m (0.2.0) + mutex_m (0.3.0) nanaimo (0.4.0) nap (1.1.0) naturally (2.2.1) @@ -218,7 +218,7 @@ GEM optparse (0.6.0) os (1.1.4) plist (3.7.1) - pstore (0.1.3) + pstore (0.1.4) public_suffix (6.0.1) rake (13.2.1) rchardet (1.8.0) @@ -228,7 +228,7 @@ GEM uber (< 0.2.0) retriable (3.1.2) rexml (3.3.9) - rouge (2.0.7) + rouge (3.28.0) ruby2_keywords (0.0.5) rubyzip (2.3.2) sawyer (0.9.2) @@ -265,8 +265,8 @@ GEM colored2 (~> 3.1) nanaimo (~> 0.4.0) rexml (>= 3.3.6, < 4.0) - xcpretty (0.3.0) - rouge (~> 2.0.7) + xcpretty (0.4.0) + rouge (~> 3.28.0) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) xcresult (0.2.2) diff --git a/WireAPI/.swiftpm/xcode/xcshareddata/xcschemes/WireAPISupport.xcscheme b/WireAPI/.swiftpm/xcode/xcshareddata/xcschemes/WireAPISupport.xcscheme deleted file mode 100644 index daf73137996..00000000000 --- a/WireAPI/.swiftpm/xcode/xcshareddata/xcschemes/WireAPISupport.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WireAPI/Package.swift b/WireAPI/Package.swift index 7faf64bd428..fca52819371 100644 --- a/WireAPI/Package.swift +++ b/WireAPI/Package.swift @@ -49,7 +49,8 @@ let package = Package( .process("APIs/SelfUserAPI/Resources"), .process("APIs/UserClientsAPI/Resources"), .process("Network/PushChannel/Resources"), - .process("Authentication/Resources") + .process("Authentication/Resources"), + .process("Backend/Resources") ] ) ] diff --git a/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/FeatureConfigRepositoryError.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPI.swift similarity index 80% rename from WireDomain/Sources/WireDomain/Repositories/FeatureConfig/FeatureConfigRepositoryError.swift rename to WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPI.swift index 0ea8390e769..eeb37c4d904 100644 --- a/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/FeatureConfigRepositoryError.swift +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPI.swift @@ -18,12 +18,11 @@ import Foundation -/// Errors originating from `FeatureConfigRepository`. +public protocol AccountsAPI { -enum FeatureConfigRepositoryError: Error { - - /// Unable to fetch feature locally - - case failedToFetchFeatureLocally + /// Convert the account from individual to team. + /// + /// + func upgradeToTeam(teamName: String) async throws -> UpgradedAccountTeam } diff --git a/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIBuilder.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIBuilder.swift new file mode 100644 index 00000000000..80b569282a7 --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIBuilder.swift @@ -0,0 +1,58 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +/// A builder for `AccountsAPI`. + +public struct AccountsAPIBuilder { + + let apiService: any APIServiceProtocol + + /// Create a new builder. + /// + /// - Parameter apiService: An api service. + + public init(apiService: any APIServiceProtocol) { + self.apiService = apiService + } + + /// Make a versioned `AccountsAPI`. + /// + /// - Parameter version: An api version. + /// - Returns: A versioned `AccountsAPI`. + + public func makeAPI(for version: APIVersion) -> any AccountsAPI { + switch version { + case .v0: + AccountsAPIV0(apiService: apiService) + case .v1: + AccountsAPIV1(apiService: apiService) + case .v2: + AccountsAPIV2(apiService: apiService) + case .v3: + AccountsAPIV3(apiService: apiService) + case .v4: + AccountsAPIV4(apiService: apiService) + case .v5: + AccountsAPIV5(apiService: apiService) + case .v6: + AccountsAPIV6(apiService: apiService) + case .v7: + AccountsAPIV7(apiService: apiService) + } + } +} diff --git a/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIError.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIError.swift new file mode 100644 index 00000000000..a810383b5bc --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIError.swift @@ -0,0 +1,35 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +/// Errors originating from `AccountsAPI`. +public enum AccountsAPIError: Error { + /// An error occurred while encoding the request body. + case invalidRequestBody + + /// A request url is invalid. + case invalidURL + + /// Unsupported endpoint for API version + case unsupportedEndpointForAPIVersion + + /// The user is already in a team + case userAlreadyInATeam + + /// The user could not be found + case userNotFound +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+SearchHeaderViewControllerDelegate.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV0.swift similarity index 64% rename from wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+SearchHeaderViewControllerDelegate.swift rename to WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV0.swift index b9445f6640b..5eb8b4f538c 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+SearchHeaderViewControllerDelegate.swift +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV0.swift @@ -16,16 +16,20 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -extension ContactsViewController: SearchHeaderViewControllerDelegate { +import Foundation - func searchHeaderViewController( - _ searchHeaderViewController: SearchHeaderViewController, - updatedSearchQuery query: String - ) { - dataSource.searchQuery = query +class AccountsAPIV0: AccountsAPI, VersionedAPI { + let apiService: any APIServiceProtocol + + init(apiService: any APIServiceProtocol) { + self.apiService = apiService + } + + var apiVersion: APIVersion { + .v0 } - func searchHeaderViewControllerDidConfirmAction(_ searchHeaderViewController: SearchHeaderViewController) { - // No op + func upgradeToTeam(teamName: String) async throws -> UpgradedAccountTeam { + throw AccountsAPIError.unsupportedEndpointForAPIVersion } } diff --git a/WireAnalytics/Sources/WireDatadog/LogLevel.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV1.swift similarity index 79% rename from WireAnalytics/Sources/WireDatadog/LogLevel.swift rename to WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV1.swift index 7b4acfabcf1..6411912d905 100644 --- a/WireAnalytics/Sources/WireDatadog/LogLevel.swift +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV1.swift @@ -16,13 +16,8 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -public extension WireDatadog { - enum LogLevel: String, Codable { - case debug - case info - case notice - case warn - case error - case critical +class AccountsAPIV1: AccountsAPIV0 { + override var apiVersion: APIVersion { + .v1 } } diff --git a/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV2.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV2.swift new file mode 100644 index 00000000000..49cc3d9c6f0 --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV2.swift @@ -0,0 +1,23 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +class AccountsAPIV2: AccountsAPIV1 { + override var apiVersion: APIVersion { + .v2 + } +} diff --git a/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV3.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV3.swift new file mode 100644 index 00000000000..8fcb079fa88 --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV3.swift @@ -0,0 +1,23 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +class AccountsAPIV3: AccountsAPIV2 { + override var apiVersion: APIVersion { + .v3 + } +} diff --git a/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV4.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV4.swift new file mode 100644 index 00000000000..ef4b73a1adc --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV4.swift @@ -0,0 +1,23 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +class AccountsAPIV4: AccountsAPIV3 { + override var apiVersion: APIVersion { + .v4 + } +} diff --git a/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV5.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV5.swift new file mode 100644 index 00000000000..07ebb79890d --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV5.swift @@ -0,0 +1,23 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +class AccountsAPIV5: AccountsAPIV4 { + override var apiVersion: APIVersion { + .v5 + } +} diff --git a/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV6.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV6.swift new file mode 100644 index 00000000000..dbc3b60c022 --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV6.swift @@ -0,0 +1,23 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +class AccountsAPIV6: AccountsAPIV5 { + override var apiVersion: APIVersion { + .v6 + } +} diff --git a/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV7.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV7.swift new file mode 100644 index 00000000000..502bb71674c --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/AccountsAPIV7.swift @@ -0,0 +1,60 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +class AccountsAPIV7: AccountsAPIV6 { + override var apiVersion: APIVersion { + .v7 + } + + override func upgradeToTeam(teamName: String) async throws -> UpgradedAccountTeam { + let components = URLComponents(string: "upgrade-personal-to-team") + + guard let url = components?.url else { + assertionFailure("generated an invalid url") + throw AccountsAPIError.invalidURL + } + + let body = UpgradeToTeamRequestBodyV7(name: teamName) + + let encodedJSON: Data + do { + encodedJSON = try JSONEncoder().encode(body) + } catch { + assertionFailure("failed to encode body") + throw AccountsAPIError.invalidRequestBody + } + + let request = URLRequestBuilder(url: url) + .withBody(encodedJSON, contentType: .json) + .withMethod(.post) + .build() + + let (data, response) = try await apiService.executeRequest( + request, + requiringAccessToken: true + ) + + return try ResponseParser() + .success(code: .ok, type: UpgradeToTeamResponseV7.self) + .failure(code: .forbidden, label: "user-already-in-a-team", error: AccountsAPIError.userAlreadyInATeam) + .failure(code: .notFound, label: "not-found", error: AccountsAPIError.userNotFound) + .parse(code: response.statusCode, data: data) + } +} diff --git a/WireAPI/Sources/WireAPI/APIs/AccountsAPI/Requests/UpgradeToTeamRequestBodyV7.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/Requests/UpgradeToTeamRequestBodyV7.swift new file mode 100644 index 00000000000..47316b2d4db --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/Requests/UpgradeToTeamRequestBodyV7.swift @@ -0,0 +1,38 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +struct UpgradeToTeamRequestBodyV7: Codable, Sendable { + let currency: String? + let icon: String + let icon_key: String? + let name: String + + init( + currency: String? = nil, + icon: String = "default", + icon_key: String? = nil, + name: String + ) { + self.currency = currency + self.icon = icon + self.icon_key = icon_key + self.name = name + } +} diff --git a/WireAPI/Sources/WireAPI/APIs/AccountsAPI/Responses/UpgradeToTeamResponseV7.swift b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/Responses/UpgradeToTeamResponseV7.swift new file mode 100644 index 00000000000..4f620dd7a94 --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/AccountsAPI/Responses/UpgradeToTeamResponseV7.swift @@ -0,0 +1,32 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +struct UpgradeToTeamResponseV7: Decodable, ToAPIModelConvertible, Sendable { + + /// The team's ID. + public let teamId: UUID + + /// The team's name. + public let teamName: String + + func toAPIModel() -> UpgradedAccountTeam { + UpgradedAccountTeam(teamId: teamId, teamName: teamName) + } +} diff --git a/WireAPI/Sources/WireAPI/APIs/BackendInfoAPI/BackendInfoAPIImpl.swift b/WireAPI/Sources/WireAPI/APIs/BackendInfoAPI/BackendInfoAPIImpl.swift index 990c863eaa3..76aa9e8dc6a 100644 --- a/WireAPI/Sources/WireAPI/APIs/BackendInfoAPI/BackendInfoAPIImpl.swift +++ b/WireAPI/Sources/WireAPI/APIs/BackendInfoAPI/BackendInfoAPIImpl.swift @@ -55,6 +55,7 @@ private struct BackendInfoResponse: Decodable, ToAPIModelConvertible { .init( domain: domain, isFederationEnabled: federation, + isMLSEnabled: false, supportedVersions: Set(supported.compactMap(APIVersion.init)), developmentVersions: Set(development?.compactMap(APIVersion.init) ?? []) ) diff --git a/WireAPI/Sources/WireAPI/APIs/ConnectionsAPI/ConnectionsAPIBuilder.swift b/WireAPI/Sources/WireAPI/APIs/ConnectionsAPI/ConnectionsAPIBuilder.swift index 143079d58a9..0e82fda2d8e 100644 --- a/WireAPI/Sources/WireAPI/APIs/ConnectionsAPI/ConnectionsAPIBuilder.swift +++ b/WireAPI/Sources/WireAPI/APIs/ConnectionsAPI/ConnectionsAPIBuilder.swift @@ -53,6 +53,8 @@ public struct ConnectionsAPIBuilder { ConnectionsAPIV5(httpClient: httpClient) case .v6: ConnectionsAPIV6(httpClient: httpClient) + case .v7: + ConnectionsAPIV7(httpClient: httpClient) } } diff --git a/WireAPI/Sources/WireAPI/APIs/ConnectionsAPI/ConnectionsAPIV7.swift b/WireAPI/Sources/WireAPI/APIs/ConnectionsAPI/ConnectionsAPIV7.swift new file mode 100644 index 00000000000..f9d28ffa97a --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/ConnectionsAPI/ConnectionsAPIV7.swift @@ -0,0 +1,21 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +final class ConnectionsAPIV7: ConnectionsAPIV6 { + override var apiVersion: APIVersion { .v7 } +} diff --git a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPI.swift b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPI.swift index cbd84f28e35..2d2ab03653b 100644 --- a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPI.swift +++ b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPI.swift @@ -41,4 +41,12 @@ public protocol ConversationsAPI { in domain: String ) async throws -> Conversation + /// Fetches the guest link for a given conversation. + /// - parameter conversationID: The conversation identifier. + /// - returns: The conversation guest link. + + func getConversationGuestLink( + conversationID: String + ) async throws -> String? + } diff --git a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIBuilder.swift b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIBuilder.swift index 2d638680564..ced67191ca9 100644 --- a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIBuilder.swift +++ b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIBuilder.swift @@ -21,13 +21,14 @@ import Foundation /// Builder for the conversations API. public struct ConversationsAPIBuilder { - private let httpClient: any HTTPClient + let apiService: any APIServiceProtocol - /// Create a new builder for the conversations API. + /// Create a new builder. /// - /// - Parameter httpClient: A http client. - public init(httpClient: any HTTPClient) { - self.httpClient = httpClient + /// - Parameter APIService: An api service. + + public init(apiService: any APIServiceProtocol) { + self.apiService = apiService } /// Make a versioned `ConversationsAPI`. @@ -37,19 +38,21 @@ public struct ConversationsAPIBuilder { public func makeAPI(for version: APIVersion) -> any ConversationsAPI { switch version { case .v0: - ConversationsAPIV0(httpClient: httpClient) + ConversationsAPIV0(apiService: apiService) case .v1: - ConversationsAPIV1(httpClient: httpClient) + ConversationsAPIV1(apiService: apiService) case .v2: - ConversationsAPIV2(httpClient: httpClient) + ConversationsAPIV2(apiService: apiService) case .v3: - ConversationsAPIV3(httpClient: httpClient) + ConversationsAPIV3(apiService: apiService) case .v4: - ConversationsAPIV4(httpClient: httpClient) + ConversationsAPIV4(apiService: apiService) case .v5: - ConversationsAPIV5(httpClient: httpClient) + ConversationsAPIV5(apiService: apiService) case .v6: - ConversationsAPIV6(httpClient: httpClient) + ConversationsAPIV6(apiService: apiService) + case .v7: + ConversationsAPIV7(apiService: apiService) } } diff --git a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIError.swift b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIError.swift index 2e0f8d3469a..adf98cd43e4 100644 --- a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIError.swift +++ b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIError.swift @@ -19,6 +19,9 @@ /// Errors originating from `ConversationsAPI`. public enum ConversationsAPIError: Error { + /// A request url is invalid. + case invalidURL + /// Failure if functionality has not been implemented. case notImplemented @@ -37,4 +40,19 @@ public enum ConversationsAPIError: Error { /// Failure if user and domain are empty case userAndDomainShouldNotBeEmpty + /// Access denied + case accessDenied + + /// Conversation not found + case conversationNotFound + + /// Conversation code not found + case conversationCodeNotFound + + /// Conversation guests links disabled + case guestLinksDisabled + + /// Invalid conversation id + case invalidConversationID + } diff --git a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV0.swift b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV0.swift index e2b33fa8802..4387aa55472 100644 --- a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV0.swift +++ b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV0.swift @@ -28,14 +28,18 @@ class ConversationsAPIV0: ConversationsAPI, VersionedAPI { // MARK: - Properties + let apiService: any APIServiceProtocol + var apiVersion: APIVersion { .v0 } - let httpClient: any HTTPClient + var basePath: String { + "/conversations" + } // MARK: - Initialize - init(httpClient: any HTTPClient) { - self.httpClient = httpClient + init(apiService: any APIServiceProtocol) { + self.apiService = apiService } func getLegacyConversationIdentifiers() async throws -> PayloadPager { @@ -48,24 +52,32 @@ class ConversationsAPIV0: ConversationsAPI, VersionedAPI { // As soon as APIVersion.v0 is removed, the legacy function can be deleted, making the code clean and easy to // understand. - let resourcePath = "/conversations/list-ids/" + let components = URLComponents(string: "\(basePath)/list-ids/") let jsonEncoder = JSONEncoder.defaultEncoder + guard let url = components?.url else { + assertionFailure("generated an invalid url") + throw ConversationsAPIError.invalidURL + } + return PayloadPager { start in // body Params let params = PaginationRequest(pagingState: start, size: Constants.batchSize) let body = try jsonEncoder.encode(params) - let request = HTTPRequest( - path: resourcePath, - method: .post, - body: body + let request = URLRequestBuilder(url: url) + .withMethod(.post) + .withBody(body, contentType: .json) + .build() + + let (data, response) = try await self.apiService.executeRequest( + request, + requiringAccessToken: true ) - let response = try await self.httpClient.executeRequest(request) return try ResponseParser() .success(code: .ok, type: PaginatedConversationIDsV0.self) - .parse(response) + .parse(code: response.statusCode, data: data) } } @@ -77,19 +89,27 @@ class ConversationsAPIV0: ConversationsAPI, VersionedAPI { func getConversations(for identifiers: [QualifiedID]) async throws -> ConversationList { let parameters = GetConversationsParametersV0(qualifiedIdentifiers: identifiers) let body = try JSONEncoder.defaultEncoder.encode(parameters) - let resourcePath = "\(pathPrefix)/conversations/list/v2" + var components = URLComponents(string: "\(pathPrefix)\(basePath)/list/v2") + + guard let url = components?.url else { + assertionFailure("generated an invalid url") + throw ConversationsAPIError.invalidURL + } + + let request = URLRequestBuilder(url: url) + .withMethod(.post) + .withBody(body, contentType: .json) + .build() - let request = HTTPRequest( - path: resourcePath, - method: .post, - body: body + let (data, response) = try await apiService.executeRequest( + request, + requiringAccessToken: true ) - let response = try await httpClient.executeRequest(request) return try ResponseParser() .success(code: .ok, type: QualifiedConversationListV0.self) .failure(code: .badRequest, error: ConversationsAPIError.invalidBody) - .parse(response) + .parse(code: response.statusCode, data: data) } func getMLSOneToOneConversation( @@ -98,6 +118,40 @@ class ConversationsAPIV0: ConversationsAPI, VersionedAPI { ) async throws -> Conversation { throw ConversationsAPIError.unsupportedEndpointForAPIVersion } + + func getConversationGuestLink( + conversationID: String + ) async throws -> String? { + let components = URLComponents(string: "\(pathPrefix)\(basePath)/\(conversationID)/code") + + guard let url = components?.url else { + assertionFailure("generated an invalid url") + throw ConversationsAPIError.invalidURL + } + + let request = URLRequestBuilder(url: url) + .withMethod(.get) + .build() + + let (data, response) = try await apiService.executeRequest( + request, + requiringAccessToken: true + ) + + return try ResponseParser() + .success(code: .ok, type: ConversationCodeV0.self) + .failure(code: .forbidden, label: "access-denied", error: ConversationsAPIError.accessDenied) + .failure(code: .notFound, label: "cnv", error: ConversationsAPIError.invalidConversationID) + .failure(code: .notFound, label: "no-conversation", error: ConversationsAPIError.conversationNotFound) + .failure( + code: .notFound, + label: "no-conversation-code", + error: ConversationsAPIError.conversationCodeNotFound + ) + .failure(code: .conflict, label: "guest-links-disabled", error: ConversationsAPIError.guestLinksDisabled) + .parse(code: response.statusCode, data: data) + } + } // MARK: Encodables @@ -220,3 +274,14 @@ struct ConversationV0: Decodable, ToAPIModelConvertible { ) } } + +struct ConversationCodeV0: Decodable, ToAPIModelConvertible { + + let code: String + let key: String + let uri: String? + + func toAPIModel() -> String? { + uri + } +} diff --git a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV1.swift b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV1.swift index d5870b4c04e..0cd9ce59b5c 100644 --- a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV1.swift +++ b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV1.swift @@ -27,24 +27,32 @@ class ConversationsAPIV1: ConversationsAPIV0 { } override func getConversationIdentifiers() async throws -> PayloadPager { - let resourcePath = "\(pathPrefix)/conversations/list-ids/" + let components = URLComponents(string: "\(pathPrefix)\(basePath)/list-ids/") let jsonEncoder = JSONEncoder.defaultEncoder + guard let url = components?.url else { + assertionFailure("generated an invalid url") + throw ConversationsAPIError.invalidURL + } + return PayloadPager { start in // body Params let params = PaginationRequest(pagingState: start, size: Constants.batchSize) let body = try jsonEncoder.encode(params) - let request = HTTPRequest( - path: resourcePath, - method: .post, - body: body + let request = URLRequestBuilder(url: url) + .withMethod(.post) + .withBody(body, contentType: .json) + .build() + + let (data, response) = try await self.apiService.executeRequest( + request, + requiringAccessToken: true ) - let response = try await self.httpClient.executeRequest(request) return try ResponseParser() .success(code: .ok, type: PaginatedConversationIDsV1.self) - .parse(response) + .parse(code: response.statusCode, data: data) } } } diff --git a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV2.swift b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV2.swift index 99e53964446..aa003733fa6 100644 --- a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV2.swift +++ b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV2.swift @@ -26,18 +26,26 @@ class ConversationsAPIV2: ConversationsAPIV1 { let body = try JSONEncoder.defaultEncoder.encode(parameters) // New change for v2 - let resourcePath = "\(pathPrefix)/conversations/list" + let components = URLComponents(string: "\(pathPrefix)\(basePath)/list") - let request = HTTPRequest( - path: resourcePath, - method: .post, - body: body + guard let url = components?.url else { + assertionFailure("generated an invalid url") + throw ConversationsAPIError.invalidURL + } + + let request = URLRequestBuilder(url: url) + .withMethod(.post) + .withBody(body, contentType: .json) + .build() + + let (data, response) = try await apiService.executeRequest( + request, + requiringAccessToken: true ) - let response = try await httpClient.executeRequest(request) return try ResponseParser() .success(code: .ok, type: QualifiedConversationListV0.self) .failure(code: .badRequest, error: ConversationsAPIError.invalidBody) - .parse(response) + .parse(code: response.statusCode, data: data) } } diff --git a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV3.swift b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV3.swift index 07846885e79..41c1e575573 100644 --- a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV3.swift +++ b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV3.swift @@ -24,19 +24,28 @@ class ConversationsAPIV3: ConversationsAPIV2 { override func getConversations(for identifiers: [QualifiedID]) async throws -> ConversationList { let parameters = GetConversationsParametersV0(qualifiedIdentifiers: identifiers) let body = try JSONEncoder.defaultEncoder.encode(parameters) - let resourcePath = "\(pathPrefix)/conversations/list" - let request = HTTPRequest( - path: resourcePath, - method: .post, - body: body + let components = URLComponents(string: "\(pathPrefix)\(basePath)/list") + + guard let url = components?.url else { + assertionFailure("generated an invalid url") + throw ConversationsAPIError.invalidURL + } + + let request = URLRequestBuilder(url: url) + .withMethod(.post) + .withBody(body, contentType: .json) + .build() + + let (data, response) = try await apiService.executeRequest( + request, + requiringAccessToken: true ) - let response = try await httpClient.executeRequest(request) return try ResponseParser() .success(code: .ok, type: QualifiedConversationListV3.self) // Change in v3 .failure(code: .badRequest, error: ConversationsAPIError.invalidBody) - .parse(response) + .parse(code: response.statusCode, data: data) } } diff --git a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV4.swift b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV4.swift index fae46fb57db..2b80f4109ce 100644 --- a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV4.swift +++ b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV4.swift @@ -16,6 +16,64 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import Foundation + class ConversationsAPIV4: ConversationsAPIV3 { override var apiVersion: APIVersion { .v4 } + + override func getConversationGuestLink( + conversationID: String + ) async throws -> String? { + let components = URLComponents(string: "\(pathPrefix)\(basePath)/\(conversationID)/code") + + guard let url = components?.url else { + assertionFailure("generated an invalid url") + throw ConversationsAPIError.invalidURL + } + + let request = URLRequestBuilder(url: url) + .withMethod(.get) + .build() + + let (data, response) = try await apiService.executeRequest( + request, + requiringAccessToken: true + ) + + return try ResponseParser() + .success(code: .ok, type: ConversationCodeV4.self) // New change in v4 + .failure( + code: .badRequest, + label: "cnv", + error: ConversationsAPIError.invalidConversationID + ) // Dedicated error code in v4 + .failure(code: .forbidden, label: "access-denied", error: ConversationsAPIError.accessDenied) + .failure(code: .notFound, label: "no-conversation", error: ConversationsAPIError.conversationNotFound) + .failure( + code: .notFound, + label: "no-conversation-code", + error: ConversationsAPIError.conversationCodeNotFound + ) + .failure(code: .conflict, label: "guest-links-disabled", error: ConversationsAPIError.guestLinksDisabled) + .parse(code: response.statusCode, data: data) + } +} + +struct ConversationCodeV4: Decodable, ToAPIModelConvertible { + + let code: String + let hasPassword: Bool // Introduced in v4 + let key: String + let uri: String? + + enum CodingKeys: String, CodingKey { + case code + case hasPassword = "has_password" + case key + case uri + } + + func toAPIModel() -> String? { + uri + } } diff --git a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV5.swift b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV5.swift index 1c743c5bddd..11bd05ca5e3 100644 --- a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV5.swift +++ b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV5.swift @@ -21,22 +21,34 @@ import Foundation class ConversationsAPIV5: ConversationsAPIV4 { override var apiVersion: APIVersion { .v5 } + var oneToOneConversationsPath: String { + "\(pathPrefix)\(basePath)/one2one" + } + override func getConversations(for identifiers: [QualifiedID]) async throws -> ConversationList { let parameters = GetConversationsParametersV0(qualifiedIdentifiers: identifiers) let body = try JSONEncoder.defaultEncoder.encode(parameters) - let resourcePath = "\(pathPrefix)/conversations/list" + let components = URLComponents(string: "\(pathPrefix)\(basePath)/list") + + guard let url = components?.url else { + assertionFailure("generated an invalid url") + throw ConversationsAPIError.invalidURL + } + + let request = URLRequestBuilder(url: url) + .withMethod(.post) + .withBody(body, contentType: .json) + .build() - let request = HTTPRequest( - path: resourcePath, - method: .post, - body: body + let (data, response) = try await apiService.executeRequest( + request, + requiringAccessToken: true ) - let response = try await httpClient.executeRequest(request) // Removed in v5: remove handling of error code 400 return try ResponseParser() - .success(code: .ok, type: QualifiedConversationListV5.self) // Change in v5 - .parse(response) + .success(code: .ok, type: QualifiedConversationListV5.self) + .parse(code: response.statusCode, data: data) } override func getMLSOneToOneConversation( @@ -47,20 +59,27 @@ class ConversationsAPIV5: ConversationsAPIV4 { throw ConversationsAPIError.userAndDomainShouldNotBeEmpty } - let resourcePath = "\(pathPrefix)/conversations/one2one/\(domain)/\(userID)" + let components = URLComponents(string: "\(oneToOneConversationsPath)/\(domain)/\(userID)") - let request = HTTPRequest( - path: resourcePath, - method: .get - ) + guard let url = components?.url else { + assertionFailure("generated an invalid url") + throw ConversationsAPIError.invalidURL + } - let response = try await httpClient.executeRequest(request) + let request = URLRequestBuilder(url: url) + .withMethod(.get) + .build() + + let (data, response) = try await apiService.executeRequest( + request, + requiringAccessToken: true + ) return try ResponseParser() .success(code: .ok, type: ConversationV5.self) .failure(code: .badRequest, label: "mls-not-enabled", error: ConversationsAPIError.mlsNotEnabled) .failure(code: .forbidden, label: "not-connected", error: ConversationsAPIError.usersNotConnected) - .parse(response) + .parse(code: response.statusCode, data: data) } } diff --git a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV6.swift b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV6.swift index dc3beee99c6..9072f6ff151 100644 --- a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV6.swift +++ b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV6.swift @@ -16,6 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -final class ConversationsAPIV6: ConversationsAPIV5 { +class ConversationsAPIV6: ConversationsAPIV5 { override var apiVersion: APIVersion { .v6 } } diff --git a/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV7.swift b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV7.swift new file mode 100644 index 00000000000..b9aed645bd8 --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/ConversationsAPI/ConversationsAPIV7.swift @@ -0,0 +1,26 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +final class ConversationsAPIV7: ConversationsAPIV6 { + + override var apiVersion: APIVersion { .v7 } + + override var oneToOneConversationsPath: String { + "\(pathPrefix)/one2one-conversations" + } +} diff --git a/WireAPI/Sources/WireAPI/APIs/FeatureConfigsAPI/FeatureConfigsAPIBuilder.swift b/WireAPI/Sources/WireAPI/APIs/FeatureConfigsAPI/FeatureConfigsAPIBuilder.swift index 7f990077ad9..62f5bce7ad6 100644 --- a/WireAPI/Sources/WireAPI/APIs/FeatureConfigsAPI/FeatureConfigsAPIBuilder.swift +++ b/WireAPI/Sources/WireAPI/APIs/FeatureConfigsAPI/FeatureConfigsAPIBuilder.swift @@ -53,7 +53,8 @@ public struct FeatureConfigsAPIBuilder { FeatureConfigsAPIV5(httpClient: httpClient) case .v6: FeatureConfigsAPIV6(httpClient: httpClient) + case .v7: + FeatureConfigsAPIV7(httpClient: httpClient) } } - } diff --git a/WireAPI/Sources/WireAPI/APIs/FeatureConfigsAPI/FeatureConfigsAPIV7.swift b/WireAPI/Sources/WireAPI/APIs/FeatureConfigsAPI/FeatureConfigsAPIV7.swift new file mode 100644 index 00000000000..8083b0ee3bc --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/FeatureConfigsAPI/FeatureConfigsAPIV7.swift @@ -0,0 +1,21 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +final class FeatureConfigsAPIV7: FeatureConfigsAPIV6 { + override var apiVersion: APIVersion { .v7 } +} diff --git a/WireAPI/Sources/WireAPI/APIs/SelfUserAPI/SelfUserAPIBuilder.swift b/WireAPI/Sources/WireAPI/APIs/SelfUserAPI/SelfUserAPIBuilder.swift index d072e1c234a..a64207d1bc3 100644 --- a/WireAPI/Sources/WireAPI/APIs/SelfUserAPI/SelfUserAPIBuilder.swift +++ b/WireAPI/Sources/WireAPI/APIs/SelfUserAPI/SelfUserAPIBuilder.swift @@ -53,6 +53,8 @@ public struct SelfUserAPIBuilder { SelfUserAPIV5(httpClient: httpClient) case .v6: SelfUserAPIV6(httpClient: httpClient) + case .v7: + SelfUserAPIV7(httpClient: httpClient) } } diff --git a/WireAPI/Sources/WireAPI/APIs/SelfUserAPI/SelfUserAPIV7.swift b/WireAPI/Sources/WireAPI/APIs/SelfUserAPI/SelfUserAPIV7.swift new file mode 100644 index 00000000000..e6c44c2bc5f --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/SelfUserAPI/SelfUserAPIV7.swift @@ -0,0 +1,21 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +final class SelfUserAPIV7: SelfUserAPIV6 { + override var apiVersion: APIVersion { .v7 } +} diff --git a/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPI.swift b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPI.swift index 433751b043b..51fb3f1a354 100644 --- a/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPI.swift +++ b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPI.swift @@ -51,16 +51,16 @@ public protocol TeamsAPI { maxResults: UInt ) async throws -> [TeamMember] - /// Get the legalhold status of a team member. + /// Get the legalhold of a team member. /// /// - Parameters: /// - teamID: The id of the team. /// - userID: The id of the member. - /// - Returns: The legalhold status of the member. + /// - Returns: The legalhold of the member. - func getLegalholdStatus( + func getLegalholdInfo( for teamID: Team.ID, userID: UUID - ) async throws -> LegalholdStatus + ) async throws -> TeamMemberLegalholdInfo } diff --git a/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIBuilder.swift b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIBuilder.swift index b37717cec57..fe4ce3da859 100644 --- a/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIBuilder.swift +++ b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIBuilder.swift @@ -53,7 +53,8 @@ public struct TeamsAPIBuilder { TeamsAPIV5(httpClient: httpClient) case .v6: TeamsAPIV6(httpClient: httpClient) + case .v7: + TeamsAPIV7(httpClient: httpClient) } } - } diff --git a/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV0.swift b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV0.swift index d83a9dc193e..85fb180df69 100644 --- a/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV0.swift +++ b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV0.swift @@ -96,12 +96,12 @@ class TeamsAPIV0: TeamsAPI, VersionedAPI { .parse(response) } - // MARK: - Get legalhold status + // MARK: - Get team member legalhold - func getLegalholdStatus( + func getLegalholdInfo( for teamID: Team.ID, userID: UUID - ) async throws -> LegalholdStatus { + ) async throws -> TeamMemberLegalholdInfo { let request = HTTPRequest( path: "\(basePath(for: teamID))/legalhold/\(userID.transportString())", method: .get @@ -110,7 +110,7 @@ class TeamsAPIV0: TeamsAPI, VersionedAPI { let response = try await httpClient.executeRequest(request) return try ResponseParser() - .success(code: .ok, type: LegalholdStatusResponseV0.self) + .success(code: .ok, type: TeamMemberLegalholdResponseV0.self) .failure(code: .notFound, error: TeamsAPIError.invalidRequest) .failure(code: .notFound, label: "no-team-member", error: TeamsAPIError.teamMemberNotFound) .parse(response) @@ -301,15 +301,35 @@ enum LegalholdStatusV0: String, Decodable { .noConsent } } +} + +struct LegalHoldLastPrekeyV0: Decodable, ToAPIModelConvertible { + let id: Int + let key: String + func toAPIModel() -> Prekey { + Prekey( + id: id, + base64EncodedKey: key + ) + } } -struct LegalholdStatusResponseV0: Decodable, ToAPIModelConvertible { +struct TeamMemberLegalholdResponseV0: Decodable, ToAPIModelConvertible { + let lastPrekey: LegalHoldLastPrekeyV0 let status: LegalholdStatusV0 - func toAPIModel() -> LegalholdStatus { - status.toAPIModel() + enum CodingKeys: String, CodingKey { + case status + case lastPrekey = "last_prekey" + } + + func toAPIModel() -> TeamMemberLegalholdInfo { + TeamMemberLegalholdInfo( + status: status.toAPIModel(), + prekey: lastPrekey.toAPIModel() + ) } } diff --git a/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV4.swift b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV4.swift index 686ed0ef421..e0f2cd5e68a 100644 --- a/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV4.swift +++ b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV4.swift @@ -89,12 +89,12 @@ class TeamsAPIV4: TeamsAPIV3 { .parse(response) } - // MARK: - Get legalhold status + // MARK: - Get team member legalhold - override func getLegalholdStatus( + override func getLegalholdInfo( for teamID: Team.ID, userID: UUID - ) async throws -> LegalholdStatus { + ) async throws -> TeamMemberLegalholdInfo { let request = HTTPRequest( path: "\(basePath(for: teamID))/legalhold/\(userID.transportString())", method: .get @@ -104,7 +104,7 @@ class TeamsAPIV4: TeamsAPIV3 { // New: 400 return try ResponseParser() - .success(code: .ok, type: LegalholdStatusResponseV0.self) + .success(code: .ok, type: TeamMemberLegalholdResponseV0.self) .failure(code: .badRequest, error: TeamsAPIError.invalidRequest) .failure(code: .notFound, label: "no-team-member", error: TeamsAPIError.teamMemberNotFound) .parse(response) diff --git a/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV5.swift b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV5.swift index 48293daf146..715f95a78db 100644 --- a/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV5.swift +++ b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV5.swift @@ -86,12 +86,12 @@ class TeamsAPIV5: TeamsAPIV4 { .parse(response) } - // MARK: - Get legalhold status + // MARK: - Get team member legalhold - override func getLegalholdStatus( + override func getLegalholdInfo( for teamID: Team.ID, userID: UUID - ) async throws -> LegalholdStatus { + ) async throws -> TeamMemberLegalholdInfo { let request = HTTPRequest( path: "\(basePath(for: teamID))/legalhold/\(userID.transportString())", method: .get @@ -101,7 +101,7 @@ class TeamsAPIV5: TeamsAPIV4 { // New: 404 invalid request. return try ResponseParser() - .success(code: .ok, type: LegalholdStatusResponseV0.self) + .success(code: .ok, type: TeamMemberLegalholdResponseV0.self) .failure(code: .notFound, error: TeamsAPIError.invalidRequest) .failure(code: .notFound, label: "no-team-member", error: TeamsAPIError.teamMemberNotFound) .parse(response) diff --git a/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV7.swift b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV7.swift new file mode 100644 index 00000000000..8f90f4e9425 --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/TeamsAPI/TeamsAPIV7.swift @@ -0,0 +1,21 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +final class TeamsAPIV7: TeamsAPIV6 { + override var apiVersion: APIVersion { .v7 } +} diff --git a/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Event decoding/Conversation/ConversationMemberLeaveEventDecoder.swift b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Event decoding/Conversation/ConversationMemberLeaveEventDecoder.swift index 4de08eeae45..c303e36476e 100644 --- a/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Event decoding/Conversation/ConversationMemberLeaveEventDecoder.swift +++ b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Event decoding/Conversation/ConversationMemberLeaveEventDecoder.swift @@ -48,7 +48,7 @@ struct ConversationMemberLeaveEventDecoder { senderID: senderID, timestamp: timestamp.date, removedUserIDs: payload.userIDs, - reason: payload.reason ?? .left + reason: payload.reason ?? .userLeft ) } diff --git a/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Event decoding/Conversation/ConversationReceiptModeUpdateEventDecoder.swift b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Event decoding/Conversation/ConversationReceiptModeUpdateEventDecoder.swift index aedb8128342..9c0c62cfd24 100644 --- a/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Event decoding/Conversation/ConversationReceiptModeUpdateEventDecoder.swift +++ b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Event decoding/Conversation/ConversationReceiptModeUpdateEventDecoder.swift @@ -41,7 +41,7 @@ struct ConversationReceiptModeUpdateEventDecoder { return ConversationReceiptModeUpdateEvent( conversationID: conversationID, senderID: senderID, - newRecieptMode: payload.receiptMode + newReceiptMode: payload.receiptMode ) } diff --git a/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Responses/UpdateEventEnvelopeV0.swift b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Responses/UpdateEventEnvelopeV0.swift index 3a9b4682da0..30d51fd5bff 100644 --- a/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Responses/UpdateEventEnvelopeV0.swift +++ b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/Responses/UpdateEventEnvelopeV0.swift @@ -18,7 +18,9 @@ import Foundation -struct UpdateEventEnvelopeV0: Decodable, ToAPIModelConvertible { +// TODO: [WPB-9612] make internal +// This is public for testing purposes. +public struct UpdateEventEnvelopeV0: Decodable, ToAPIModelConvertible { let id: UUID let payload: [UpdateEventDecodingProxy]? diff --git a/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/UpdateEventsAPIBuilder.swift b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/UpdateEventsAPIBuilder.swift index 4a41613b19e..65caf64ed4a 100644 --- a/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/UpdateEventsAPIBuilder.swift +++ b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/UpdateEventsAPIBuilder.swift @@ -53,6 +53,8 @@ public struct UpdateEventsAPIBuilder { UpdateEventsAPIV5(apiService: apiService) case .v6: UpdateEventsAPIV6(apiService: apiService) + case .v7: + UpdateEventsAPIV7(apiService: apiService) } } diff --git a/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/UpdateEventsAPIError.swift b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/UpdateEventsAPIError.swift index 9989e915635..4745ff3f764 100644 --- a/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/UpdateEventsAPIError.swift +++ b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/UpdateEventsAPIError.swift @@ -22,7 +22,7 @@ import Foundation public enum UpdateEventsAPIError: Error { - /// A request url is not invalid. + /// A request url is invalid. case invalidURL diff --git a/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/UpdateEventsAPIV7.swift b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/UpdateEventsAPIV7.swift new file mode 100644 index 00000000000..ae23f154228 --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/UpdateEventsAPI/UpdateEventsAPIV7.swift @@ -0,0 +1,21 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +final class UpdateEventsAPIV7: UpdateEventsAPIV6 { + override var apiVersion: APIVersion { .v7 } +} diff --git a/WireAPI/Sources/WireAPI/APIs/UserClientsAPI/UserClientsAPIBuilder.swift b/WireAPI/Sources/WireAPI/APIs/UserClientsAPI/UserClientsAPIBuilder.swift index 2d2366c52fb..b15d5045af4 100644 --- a/WireAPI/Sources/WireAPI/APIs/UserClientsAPI/UserClientsAPIBuilder.swift +++ b/WireAPI/Sources/WireAPI/APIs/UserClientsAPI/UserClientsAPIBuilder.swift @@ -53,6 +53,8 @@ public struct UserClientsAPIBuilder { UserClientsAPIV5(apiService: apiService) case .v6: UserClientsAPIV6(apiService: apiService) + case .v7: + UserClientsAPIV7(apiService: apiService) } } diff --git a/WireAPI/Sources/WireAPI/APIs/UserClientsAPI/UserClientsAPIError.swift b/WireAPI/Sources/WireAPI/APIs/UserClientsAPI/UserClientsAPIError.swift index 0dce6180081..01017288662 100644 --- a/WireAPI/Sources/WireAPI/APIs/UserClientsAPI/UserClientsAPIError.swift +++ b/WireAPI/Sources/WireAPI/APIs/UserClientsAPI/UserClientsAPIError.swift @@ -22,7 +22,7 @@ import Foundation public enum UserClientsAPIError: Error { - /// A request url is not invalid. + /// A request url is invalid. case invalidURL } diff --git a/WireAPI/Sources/WireAPI/APIs/UserClientsAPI/UserClientsAPIV7.swift b/WireAPI/Sources/WireAPI/APIs/UserClientsAPI/UserClientsAPIV7.swift new file mode 100644 index 00000000000..6d989837898 --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/UserClientsAPI/UserClientsAPIV7.swift @@ -0,0 +1,108 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +final class UserClientsAPIV7: UserClientsAPIV6 { + + override var apiVersion: APIVersion { .v7 } + + override func getSelfClients() async throws -> [SelfUserClient] { + let components = URLComponents(string: "\(pathPrefix)/clients") + + guard let url = components?.url else { + assertionFailure("generated an invalid url") + throw UserClientsAPIError.invalidURL + } + + let request = URLRequestBuilder(url: url) + .withMethod(.get) + .build() + + let (data, response) = try await apiService.executeRequest( + request, + requiringAccessToken: true + ) + + return try ResponseParser() + .success(code: .ok, type: ListUserClientV7.self) + .parse(code: response.statusCode, data: data) + } +} + +// SelfUserClientV7.capabilities is now a list and not nested within another object anymore. + +private struct ListUserClientV7: Decodable, ToAPIModelConvertible { + + let payload: [SelfUserClientV7] + + init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + let payload = try container.decode([SelfUserClientV7].self) + self.payload = payload + } + + func toAPIModel() -> [SelfUserClient] { + payload.map { $0.toAPIModel() } + } +} + +private struct SelfUserClientV7: Decodable, ToAPIModelConvertible { + + let id: String + let type: UserClientType + let activationDate: UTCTimeMillis + let label: String? + let model: String? + let deviceClass: DeviceClass? + let lastActiveDate: UTCTime? + let mlsPublicKeys: MLSPublicKeys? + let cookie: String? + let capabilities: [UserClientCapability]? // not of type `CapabilitiesList` anymore + + enum CodingKeys: String, CodingKey { + + case id + case type + case activationDate = "time" + case label + case model + case deviceClass = "class" + case lastActiveDate = "last_active" + case mlsPublicKeys = "mls_public_keys" + case cookie + case capabilities + + } + + func toAPIModel() -> SelfUserClient { + SelfUserClient( + id: id, + type: type, + activationDate: activationDate.date, + label: label, + model: model, + deviceClass: deviceClass, + lastActiveDate: lastActiveDate?.date, + mlsPublicKeys: mlsPublicKeys, + cookie: cookie, + capabilities: capabilities ?? [] + ) + } + +} diff --git a/WireAPI/Sources/WireAPI/APIs/UserPropertiesAPI/UserPropertiesAPIBuilder.swift b/WireAPI/Sources/WireAPI/APIs/UserPropertiesAPI/UserPropertiesAPIBuilder.swift index 64bea8fe1c5..c31d910dee6 100644 --- a/WireAPI/Sources/WireAPI/APIs/UserPropertiesAPI/UserPropertiesAPIBuilder.swift +++ b/WireAPI/Sources/WireAPI/APIs/UserPropertiesAPI/UserPropertiesAPIBuilder.swift @@ -53,6 +53,8 @@ public struct UserPropertiesBuilder { UserPropertiesAPIV5(httpClient: httpClient) case .v6: UserPropertiesAPIV6(httpClient: httpClient) + case .v7: + UserPropertiesAPIV7(httpClient: httpClient) } } diff --git a/WireAPI/Sources/WireAPI/APIs/UserPropertiesAPI/UserPropertiesAPIV7.swift b/WireAPI/Sources/WireAPI/APIs/UserPropertiesAPI/UserPropertiesAPIV7.swift new file mode 100644 index 00000000000..19efbe858f8 --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/UserPropertiesAPI/UserPropertiesAPIV7.swift @@ -0,0 +1,21 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +final class UserPropertiesAPIV7: UserPropertiesAPIV6 { + override var apiVersion: APIVersion { .v7 } +} diff --git a/WireAPI/Sources/WireAPI/APIs/UsersAPI/UsersAPIBuilder.swift b/WireAPI/Sources/WireAPI/APIs/UsersAPI/UsersAPIBuilder.swift index d4fe40803c7..66325e9ff44 100644 --- a/WireAPI/Sources/WireAPI/APIs/UsersAPI/UsersAPIBuilder.swift +++ b/WireAPI/Sources/WireAPI/APIs/UsersAPI/UsersAPIBuilder.swift @@ -53,7 +53,8 @@ public struct UsersAPIBuilder { UsersAPIV5(httpClient: httpClient) case .v6: UsersAPIV6(httpClient: httpClient) + case .v7: + UsersAPIV7(httpClient: httpClient) } } - } diff --git a/WireAPI/Sources/WireAPI/APIs/UsersAPI/UsersAPIV7.swift b/WireAPI/Sources/WireAPI/APIs/UsersAPI/UsersAPIV7.swift new file mode 100644 index 00000000000..d87bfd4b2ff --- /dev/null +++ b/WireAPI/Sources/WireAPI/APIs/UsersAPI/UsersAPIV7.swift @@ -0,0 +1,21 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +final class UsersAPIV7: UsersAPIV6 { + override var apiVersion: APIVersion { .v7 } +} diff --git a/WireAPI/Sources/WireAPI/Assembly.swift b/WireAPI/Sources/WireAPI/Assembly.swift new file mode 100644 index 00000000000..9034110f09f --- /dev/null +++ b/WireAPI/Sources/WireAPI/Assembly.swift @@ -0,0 +1,93 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation +import WireFoundation + +public final class Assembly { + + let userID: UUID + let clientID: String + let backendEnvironment: BackendEnvironment + let minTLSVersion: TLSVersion + let cookieEncryptionKey: Data + + public init( + userID: UUID, + clientID: String, + backendEnvironment: BackendEnvironment, + minTLSVersion: TLSVersion, + cookieEncryptionKey: Data + ) { + self.userID = userID + self.clientID = clientID + self.backendEnvironment = backendEnvironment + self.minTLSVersion = minTLSVersion + self.cookieEncryptionKey = cookieEncryptionKey + } + + private lazy var keychain: some KeychainProtocol = Keychain() + private lazy var urlSessionConfigurationFactory = URLSessionConfigurationFactory( + minTLSVersion: minTLSVersion, + proxySettings: backendEnvironment.proxySettings + ) + + private lazy var apiService: some APIServiceProtocol = APIService( + networkService: apiNetworkService, + authenticationManager: authenticationManager + ) + + public lazy var apiNetworkService: NetworkService = { + let service = NetworkService(baseURL: backendEnvironment.url, serverTrustValidator: serverTrustValidator) + let config = urlSessionConfigurationFactory.makeRESTAPISessionConfiguration() + let session = URLSession(configuration: config, delegate: service, delegateQueue: nil) + service.configure(with: session) + return service + }() + + private lazy var pushChannelService: some PushChannelServiceProtocol = PushChannelService( + networkService: pushChannelNetworkService, + authenticationManager: authenticationManager + ) + + private lazy var pushChannelNetworkService: NetworkService = { + let service = NetworkService( + baseURL: backendEnvironment.webSocketURL, + serverTrustValidator: serverTrustValidator + ) + let config = urlSessionConfigurationFactory.makeWebSocketSessionConfiguration() + let session = URLSession(configuration: config, delegate: service, delegateQueue: nil) + service.configure(with: session) + return service + }() + + public lazy var authenticationManager: some AuthenticationManagerProtocol = AuthenticationManager( + clientID: clientID, + cookieStorage: cookieStorage, + networkService: apiNetworkService + ) + + private lazy var cookieStorage: some CookieStorageProtocol = CookieStorage( + userID: userID, + cookieEncryptionKey: cookieEncryptionKey, + keychain: keychain + ) + + private lazy var serverTrustValidator = ServerTrustValidator(pinnedKeys: backendEnvironment.pinnedKeys) + +} diff --git a/WireAPI/Sources/WireAPI/Authentication/AuthenticationManager.swift b/WireAPI/Sources/WireAPI/Authentication/AuthenticationManager.swift index 98e24c933ae..e81fdf1e26c 100644 --- a/WireAPI/Sources/WireAPI/Authentication/AuthenticationManager.swift +++ b/WireAPI/Sources/WireAPI/Authentication/AuthenticationManager.swift @@ -20,7 +20,7 @@ import Foundation import WireFoundation // sourcery: AutoMockable -protocol AuthenticationManagerProtocol { +public protocol AuthenticationManagerProtocol { func getValidAccessToken() async throws -> AccessToken func refreshAccessToken() async throws -> AccessToken diff --git a/WireAPI/Sources/WireAPI/Authentication/AuthenticationStorage.swift b/WireAPI/Sources/WireAPI/Authentication/AuthenticationStorage.swift deleted file mode 100644 index 1521306b5e3..00000000000 --- a/WireAPI/Sources/WireAPI/Authentication/AuthenticationStorage.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Foundation - -/// Storage for authentication primitives. - -public protocol AuthenticationStorage { - - /// Store an access token. - /// - /// - Parameter accessToken: The token to store. - - func storeAccessToken(_ accessToken: AccessToken) async - - /// Fetch a stored access token. - /// - /// - Returns: The stored access token. - - func fetchAccessToken() async -> AccessToken? - - /// Store cookies. - /// - /// - Parameter cookies: The cookies to store. - - func storeCookies(_ cookies: [HTTPCookie]) async throws - - /// Fetch stored cookies. - /// - /// - Returns: The stored cookies. - - func fetchCookies() async throws -> [HTTPCookie] - -} diff --git a/WireAPI/Sources/WireAPI/Components/ResponseParser.swift b/WireAPI/Sources/WireAPI/Components/ResponseParser.swift index dcc210b6c44..2b42776eab5 100644 --- a/WireAPI/Sources/WireAPI/Components/ResponseParser.swift +++ b/WireAPI/Sources/WireAPI/Components/ResponseParser.swift @@ -73,7 +73,7 @@ struct ResponseParser { func failure( code: HTTPStatusCode, label: String = "", - error: any Error + error: some Error ) -> ResponseParser { var copy = self copy.parseBlocks.append { _, data in diff --git a/WireAPI/Sources/WireAPI/HTTP Client/HTTPStatusCode.swift b/WireAPI/Sources/WireAPI/HTTP Client/HTTPStatusCode.swift index 8f33ef98474..0285675e555 100644 --- a/WireAPI/Sources/WireAPI/HTTP Client/HTTPStatusCode.swift +++ b/WireAPI/Sources/WireAPI/HTTP Client/HTTPStatusCode.swift @@ -46,6 +46,10 @@ enum HTTPStatusCode: Int { case forbidden = 403 + /// conflict - 409 + + case conflict = 409 + // MARK: Server Errors - 5xx /// service unavailable - 503 diff --git a/WireAPI/Sources/WireAPI/Models/API/APIVersion.swift b/WireAPI/Sources/WireAPI/Models/API/APIVersion.swift index 294168b52a8..5a25b166ca2 100644 --- a/WireAPI/Sources/WireAPI/Models/API/APIVersion.swift +++ b/WireAPI/Sources/WireAPI/Models/API/APIVersion.swift @@ -31,6 +31,7 @@ public enum APIVersion: UInt, CaseIterable, Comparable { case v4 case v5 case v6 + case v7 /// API versions considered production ready by the client. /// @@ -42,7 +43,7 @@ public enum APIVersion: UInt, CaseIterable, Comparable { /// Only if these critera are met should we explicitly mark the version /// as production ready. - public static let productionVersions: Set = [.v0, .v1, .v2, .v3, .v4, .v5] + public static let productionVersions: Set = [.v0, .v1, .v2, .v3, .v4, .v5, .v6] /// API versions currently under development and not suitable for production /// environments. diff --git a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor.swift b/WireAPI/Sources/WireAPI/Models/Accounts/UpgradedAccountTeam.swift similarity index 58% rename from WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor.swift rename to WireAPI/Sources/WireAPI/Models/Accounts/UpgradedAccountTeam.swift index 08e9e898cea..f7060f87d5e 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor.swift +++ b/WireAPI/Sources/WireAPI/Models/Accounts/UpgradedAccountTeam.swift @@ -16,25 +16,30 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import WireAPI +import Foundation -/// Process conversation typing events. +/// The upgraded account's team information. -protocol ConversationTypingEventProcessorProtocol { +public struct UpgradedAccountTeam: Equatable, Sendable { - /// Process a conversation typing event. - /// - /// - Parameter event: A conversation typing event. - - func processEvent(_ event: ConversationTypingEvent) async throws + /// The team's ID. + public let teamId: UUID -} + /// The team's name. + public let teamName: String -struct ConversationTypingEventProcessor: ConversationTypingEventProcessorProtocol { - - func processEvent(_: ConversationTypingEvent) async throws { - // TODO: [WPB-10178] - assertionFailure("not implemented yet") + /// Create a new `UpgradedAccountTeam`. + /// + /// - Parameters: + /// - teamId: The team's ID. + /// - teamName: The team's name. + + public init( + teamId: UUID, + teamName: String + ) { + self.teamId = teamId + self.teamName = teamName } } diff --git a/WireAPI/Sources/WireAPI/Models/Backend/BackendEnvironment.swift b/WireAPI/Sources/WireAPI/Models/Backend/BackendEnvironment.swift new file mode 100644 index 00000000000..77377ad3acc --- /dev/null +++ b/WireAPI/Sources/WireAPI/Models/Backend/BackendEnvironment.swift @@ -0,0 +1,55 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +/// A collection of data for connecting to a given backend environment (e.g. Production, Staging, etc). + +public struct BackendEnvironment { + + /// The `URL` of the backend. + + let url: URL + + /// The `URL` of the WebSocket endpoint. + + let webSocketURL: URL + + /// The pinned keys for the backend for use with certificate pinning. + + let pinnedKeys: [PinnedKey] + + /// The proxy settings for the backend if any. + + let proxySettings: ProxySettings? + + /// Creates a new `BackendEnvironment`. + /// + /// - Parameter url: The `URL` of the backend. + /// - Parameter webSocketURL: The `URL` of the WebSocket endpoint. + /// - Parameter pinnedKeys: The pinned keys for the backend for use with certificate pinning. + /// - Parameter proxySettings: The proxy settings for the backend if any. + + public init(url: URL, webSocketURL: URL, pinnedKeys: [PinnedKey], proxySettings: ProxySettings?) { + self.url = url + self.webSocketURL = webSocketURL + self.pinnedKeys = pinnedKeys + self.proxySettings = proxySettings + } + +} diff --git a/WireAPI/Sources/WireAPI/Models/Backend/BackendInfo.swift b/WireAPI/Sources/WireAPI/Models/Backend/BackendInfo.swift index 1cb54c722f7..6af8106e845 100644 --- a/WireAPI/Sources/WireAPI/Models/Backend/BackendInfo.swift +++ b/WireAPI/Sources/WireAPI/Models/Backend/BackendInfo.swift @@ -30,6 +30,10 @@ public struct BackendInfo: Equatable { public let isFederationEnabled: Bool + /// Whether the backend supports MLS. + + public let isMLSEnabled: Bool + /// All production ready api versions supported by the local backend. public let supportedVersions: Set diff --git a/WireAPI/Sources/WireAPI/Models/Backend/PinnedKey.swift b/WireAPI/Sources/WireAPI/Models/Backend/PinnedKey.swift new file mode 100644 index 00000000000..511d75a4dd0 --- /dev/null +++ b/WireAPI/Sources/WireAPI/Models/Backend/PinnedKey.swift @@ -0,0 +1,74 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation +@preconcurrency import Security + +/// Associates a list of `hosts` with a public `key`. + +public struct PinnedKey: Sendable { + + public enum Failure: Error { + case invalidKeyData + } + + public enum Host: Sendable { + case endsWith(String) + case equals(String) + } + + let key: SecKey + let hosts: [Host] + + public init(key: SecKey, hosts: [Host]) { + self.key = key + self.hosts = hosts + } + + public init(key: Data, hosts: [Host]) throws(Failure) { + self.key = try Self.key(for: key) + self.hosts = hosts + } + + /// Returns `true` if `host` matches any of the `hosts` in `self`. + + func matches(host: String) -> Bool { + hosts.contains { + switch $0 { + case let .endsWith(suffix): + host.hasSuffix(suffix) + case let .equals(value): + host == value + } + } + } + + // MARK: - Private + + private static func key(for data: Data) throws(Failure) -> SecKey { + guard + let certificate = SecCertificateCreateWithData(nil, data as CFData), + let publicKey = SecCertificateCopyKey(certificate) + else { + throw Failure.invalidKeyData + } + + return publicKey + } + +} diff --git a/WireAPI/Sources/WireAPI/Models/Backend/ProxySettings.swift b/WireAPI/Sources/WireAPI/Models/Backend/ProxySettings.swift new file mode 100644 index 00000000000..059eab1547d --- /dev/null +++ b/WireAPI/Sources/WireAPI/Models/Backend/ProxySettings.swift @@ -0,0 +1,60 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +/// Proxy settings for communicating with a backend server. + +public enum ProxySettings { + + /// Settings for an unauthenticated proxy. + + case unauthenticated(host: String, port: Int) + + /// Settings for an authenticated proxy. + + case authenticated(host: String, port: Int, username: String, password: String) + + /// Dictionary to be used with `URLSessionConfiguration.connectionProxyDictionary`. + + func proxyDictionary() -> [AnyHashable: Any] { + let socksEnable = "SOCKSEnable" + let socksProxy = "SOCKSProxy" + let socksPort = "SOCKSPort" + + var result: [AnyHashable: Any] = [ + socksEnable: 1, + kCFProxyTypeKey: kCFProxyTypeSOCKS, + kCFStreamPropertySOCKSVersion: kCFStreamSocketSOCKSVersion5 + ] + + switch self { + case let .unauthenticated(host, port): + result[socksProxy] = host + result[socksPort] = port + case let .authenticated(host, port, username, password): + result[socksProxy] = host + result[socksPort] = port + result[kCFStreamPropertySOCKSUser] = username + result[kCFStreamPropertySOCKSPassword] = password + } + + return result + } + +} diff --git a/WireAPI/Sources/WireAPI/Models/Backend/ServerTrustValidator.swift b/WireAPI/Sources/WireAPI/Models/Backend/ServerTrustValidator.swift new file mode 100644 index 00000000000..011e7035c0a --- /dev/null +++ b/WireAPI/Sources/WireAPI/Models/Backend/ServerTrustValidator.swift @@ -0,0 +1,98 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation +@preconcurrency import Security + +struct ServerTrustValidator: Sendable { + + enum Failure: Error, Equatable { + case evaluatingServerTrustFailed + case noPublicKeyOnServerTrust + case noMatchingPublicKey + } + + private let pinnedKeys: [PinnedKey] + + init(pinnedKeys: [PinnedKey]) { + self.pinnedKeys = pinnedKeys + } + + /// Verifies the server `trust` for the given `host`. + /// + /// - Parameter trust: The `SecTrust` of the server. + /// - Parameter host: The host of the server. + /// - Throws: An error if server certificate should not be trusted. + /// - Note: If no pinned keys are found for the `host`, the server certificate is trusted. + + func validate(trust: SecTrust, host: String) async throws { + let matchingKeys = pinnedKeys.filter { $0.matches(host: host) }.map(\.key) + + // If no keys are pinned for `host`, we trust the server certificate + guard !matchingKeys.isEmpty else { return } + + try await Self.verifyServerCertificateTrusted(trust) + + let publicKey = try Self.publicKeyAssociatedWithServerTrust(trust) + + guard matchingKeys.contains(publicKey) else { + throw Failure.noMatchingPublicKey + } + } + + // MARK: - Private + + private static func verifyServerCertificateTrusted(_ serverTrust: SecTrust) async throws { + try await withCheckedThrowingContinuation { continuation in + // `SecTrustEvaluateAsyncWithError` requires the completion queue to be the same as the queue on which it + // is called. + let queue = DispatchQueue.global() + queue.async { + SecTrustEvaluateAsyncWithError(serverTrust, queue) { _, success, error in + if success { + continuation.resume() + } else { + print("Server trust evaluation failed: \(String(describing: error))") + continuation.resume(throwing: Failure.evaluatingServerTrustFailed) + } + } + } + } + } + + /// Returns the public key of the leaf certificate associated with `serverTrust`. + /// + /// - Parameter serverTrust: SecTrust of server + /// - Returns: public key from `serverTrust` + + private static func publicKeyAssociatedWithServerTrust(_ serverTrust: SecTrust) throws -> SecKey { + let certificates = SecTrustCopyCertificateChain(serverTrust) ?? [] as CFArray + let policy = SecPolicyCreateBasicX509() + var secTrust: SecTrust? + + guard SecTrustCreateWithCertificates(certificates, policy, &secTrust) == noErr, + let trust = secTrust, + let result = SecTrustCopyKey(trust) + else { + throw Failure.noPublicKeyOnServerTrust + } + + return result + } + +} diff --git a/WireAPI/Sources/WireAPI/Models/Conversation/ConversationMemberLeaveReason.swift b/WireAPI/Sources/WireAPI/Models/Conversation/ConversationMemberLeaveReason.swift index 176c2aecc3f..e5dc43ee6bc 100644 --- a/WireAPI/Sources/WireAPI/Models/Conversation/ConversationMemberLeaveReason.swift +++ b/WireAPI/Sources/WireAPI/Models/Conversation/ConversationMemberLeaveReason.swift @@ -29,10 +29,10 @@ public enum ConversationMemberLeaveReason: String, Codable, Sendable { /// The user left the conversation by themselves. - case left + case userLeft /// The user was removed from the conversation by an admin. - case removed + case userRemoved } diff --git a/WireAPI/Sources/WireAPI/Models/Messaging/MessageContent.swift b/WireAPI/Sources/WireAPI/Models/Messaging/MessageContent.swift index 294b520f80c..c5096233f12 100644 --- a/WireAPI/Sources/WireAPI/Models/Messaging/MessageContent.swift +++ b/WireAPI/Sources/WireAPI/Models/Messaging/MessageContent.swift @@ -21,7 +21,7 @@ import Foundation /// The contents of a message, typically as a base-64 encoded /// Protobuf string. -public enum MessageContent: Equatable, Codable { +public enum MessageContent: Equatable, Codable, Sendable { /// Encrypted message content. diff --git a/WireAPI/Sources/WireAPI/Models/Network/TLSVersion.swift b/WireAPI/Sources/WireAPI/Models/Network/TLSVersion.swift index 101460de87c..81aa816f5f5 100644 --- a/WireAPI/Sources/WireAPI/Models/Network/TLSVersion.swift +++ b/WireAPI/Sources/WireAPI/Models/Network/TLSVersion.swift @@ -30,6 +30,23 @@ public enum TLSVersion { case v1_3 + public static func minVersionFrom(_ string: String?) -> TLSVersion { + string.flatMap(TLSVersion.init) ?? .v1_2 + } + + public init?(_ string: String) { + switch string { + case "1.2": + self = .v1_2 + + case "1.3": + self = .v1_3 + + default: + return nil + } + } + var secValue: tls_protocol_version_t { switch self { case .v1_2: diff --git a/WireAPI/Sources/WireAPI/Models/SelfUser/ManagingSystem.swift b/WireAPI/Sources/WireAPI/Models/SelfUser/ManagingSystem.swift index 505e6385602..24ae2ab325b 100644 --- a/WireAPI/Sources/WireAPI/Models/SelfUser/ManagingSystem.swift +++ b/WireAPI/Sources/WireAPI/Models/SelfUser/ManagingSystem.swift @@ -20,7 +20,7 @@ import Foundation /// The managing system of the self user identity -public enum ManagingSystem { +public enum ManagingSystem: String, Sendable { /// User identity is managed with Wire diff --git a/WireAPI/Sources/WireAPI/Models/SelfUser/SSOID.swift b/WireAPI/Sources/WireAPI/Models/SelfUser/SSOID.swift index c4ac625a35e..8aba3593296 100644 --- a/WireAPI/Sources/WireAPI/Models/SelfUser/SSOID.swift +++ b/WireAPI/Sources/WireAPI/Models/SelfUser/SSOID.swift @@ -20,7 +20,7 @@ import Foundation /// The sso id of the self user -public struct SSOID: Equatable { +public struct SSOID: Equatable, Sendable { /// The self user's scim external id diff --git a/WireAPI/Sources/WireAPI/Models/SelfUser/SelfUser.swift b/WireAPI/Sources/WireAPI/Models/SelfUser/SelfUser.swift index abb730be37e..01499d4432a 100644 --- a/WireAPI/Sources/WireAPI/Models/SelfUser/SelfUser.swift +++ b/WireAPI/Sources/WireAPI/Models/SelfUser/SelfUser.swift @@ -20,7 +20,7 @@ import Foundation /// User profile for self -public struct SelfUser: Equatable { +public struct SelfUser: Equatable, Sendable { /// The unique id of the self user diff --git a/WireAPI/Sources/WireAPI/Models/Team/TeamMemberLegalholdInfo.swift b/WireAPI/Sources/WireAPI/Models/Team/TeamMemberLegalholdInfo.swift new file mode 100644 index 00000000000..d302f697334 --- /dev/null +++ b/WireAPI/Sources/WireAPI/Models/Team/TeamMemberLegalholdInfo.swift @@ -0,0 +1,39 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +public typealias LegalholdPrekey = Prekey + +/// The team member legal hold. +public struct TeamMemberLegalholdInfo: Equatable, Sendable { + + /// The legal hold status + + public let status: LegalholdStatus + + /// The legal hold prekey + + public let prekey: LegalholdPrekey + + public init( + status: LegalholdStatus, + prekey: LegalholdPrekey + ) { + self.status = status + self.prekey = prekey + } +} diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationCodeUpdateEvent.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationCodeUpdateEvent.swift index 56917c512ce..feb85f6baf3 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationCodeUpdateEvent.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationCodeUpdateEvent.swift @@ -20,7 +20,7 @@ import Foundation /// An event where the conversation's guest link code was updated. -public struct ConversationCodeUpdateEvent: Equatable, Codable { +public struct ConversationCodeUpdateEvent: Equatable, Codable, Sendable { /// The id of the conversation. diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationEvent.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationEvent.swift index ab3949715ad..b6b3fe94d87 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationEvent.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationEvent.swift @@ -20,7 +20,7 @@ import Foundation /// An event concerning conversations. -public enum ConversationEvent: Equatable, Codable { +public enum ConversationEvent: Equatable, Codable, Sendable { /// A conversation's access settings were updated. diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMLSMessageAddEvent.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMLSMessageAddEvent.swift index ce3aff250b8..cfe4d88ddf5 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMLSMessageAddEvent.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMLSMessageAddEvent.swift @@ -20,7 +20,7 @@ import Foundation /// An event where an mls message was received in a conversation. -public struct ConversationMLSMessageAddEvent: Equatable, Codable { +public struct ConversationMLSMessageAddEvent: Equatable, Codable, Sendable { /// The id of the conversation. diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMLSWelcomeEvent.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMLSWelcomeEvent.swift index fd8919a4f63..75ed4a0a214 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMLSWelcomeEvent.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMLSWelcomeEvent.swift @@ -20,7 +20,7 @@ import Foundation /// An event where an mls welcome message was received in a conversation. -public struct ConversationMLSWelcomeEvent: Equatable, Codable { +public struct ConversationMLSWelcomeEvent: Equatable, Codable, Sendable { /// The id of the conversation. diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMessageTimerUpdateEvent.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMessageTimerUpdateEvent.swift index 3dae5c6e6cc..20e87d8b54d 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMessageTimerUpdateEvent.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationMessageTimerUpdateEvent.swift @@ -20,7 +20,7 @@ import Foundation /// An event where the message timer of a conversation was updated. -public struct ConversationMessageTimerUpdateEvent: Equatable, Codable { +public struct ConversationMessageTimerUpdateEvent: Equatable, Codable, Sendable { /// The id of the conversation. diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationProteusMessageAddEvent.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationProteusMessageAddEvent.swift index 7eb33c4c70d..373c87c1996 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationProteusMessageAddEvent.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationProteusMessageAddEvent.swift @@ -20,7 +20,7 @@ import Foundation /// An event where a proteus message was received in a conversation. -public struct ConversationProteusMessageAddEvent: Equatable, Codable { +public struct ConversationProteusMessageAddEvent: Equatable, Codable, Sendable { /// The id of the conversation. diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationReceiptModeUpdateEvent.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationReceiptModeUpdateEvent.swift index 804a79a6416..880ec2075df 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationReceiptModeUpdateEvent.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationReceiptModeUpdateEvent.swift @@ -35,6 +35,6 @@ public struct ConversationReceiptModeUpdateEvent: Equatable, Codable, Sendable { /// A value of `1` indicates read reciepts are enabled /// and any other value indicates receipts are disabled. - public let newRecieptMode: Int + public let newReceiptMode: Int } diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationRenameEvent.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationRenameEvent.swift index 9f28e590038..079dfabc869 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationRenameEvent.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationRenameEvent.swift @@ -20,7 +20,7 @@ import Foundation /// An event where the conversation's name was changed. -public struct ConversationRenameEvent: Equatable, Codable { +public struct ConversationRenameEvent: Equatable, Codable, Sendable { /// The id of the conversation. diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationTypingEvent.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationTypingEvent.swift index 3e7f4977b2c..18998ce4cb6 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationTypingEvent.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/ConversationEvent/ConversationTypingEvent.swift @@ -20,7 +20,7 @@ import Foundation /// An event where a user is typing in a conversation. -public struct ConversationTypingEvent: Equatable, Codable { +public struct ConversationTypingEvent: Equatable, Codable, Sendable { /// The id of the conversation. diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/UpdateEvent.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/UpdateEvent.swift index 66fe62bae1a..322c04b1215 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/UpdateEvent.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/UpdateEvent.swift @@ -22,7 +22,7 @@ import Foundation /// that can be used to incrementaly update the state of /// the client. -public enum UpdateEvent: Equatable, Codable { +public enum UpdateEvent: Equatable, Codable, Sendable { /// A conversation event. diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/UpdateEventEnvelope.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/UpdateEventEnvelope.swift index 1b89e1e5b7c..9697724a094 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/UpdateEventEnvelope.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/UpdateEventEnvelope.swift @@ -20,7 +20,7 @@ import Foundation /// A container for update events. -public struct UpdateEventEnvelope: Equatable, Codable { +public struct UpdateEventEnvelope: Equatable, Codable, Sendable { /// The id of the event envelope. diff --git a/WireAPI/Sources/WireAPI/Models/User/UserID.swift b/WireAPI/Sources/WireAPI/Models/User/UserID.swift index db9cc419e44..576a237f690 100644 --- a/WireAPI/Sources/WireAPI/Models/User/UserID.swift +++ b/WireAPI/Sources/WireAPI/Models/User/UserID.swift @@ -18,6 +18,6 @@ import Foundation -/// Fully quallified user identifier. +/// Fully qualified user identifier. public typealias UserID = QualifiedID diff --git a/WireAPI/Sources/WireAPI/Models/UserClient/UserClientCapability.swift b/WireAPI/Sources/WireAPI/Models/UserClient/UserClientCapability.swift index 1b5462b67b4..b28548c0c48 100644 --- a/WireAPI/Sources/WireAPI/Models/UserClient/UserClientCapability.swift +++ b/WireAPI/Sources/WireAPI/Models/UserClient/UserClientCapability.swift @@ -16,8 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - /// Capabilities of a user client. public enum UserClientCapability: String, Codable, Sendable { diff --git a/WireAPI/Sources/WireAPI/Network/APIService/APIService.swift b/WireAPI/Sources/WireAPI/Network/APIService/APIService.swift index c59ef047635..70b0e334897 100644 --- a/WireAPI/Sources/WireAPI/Network/APIService/APIService.swift +++ b/WireAPI/Sources/WireAPI/Network/APIService/APIService.swift @@ -53,47 +53,7 @@ public final class APIService: APIServiceProtocol { private let networkService: NetworkService private let authenticationManager: any AuthenticationManagerProtocol - /// Create a new `APIService`. - /// - /// - Parameters: - /// - clientID: The id of the self client. - /// - backendURL: The url of the target backend. - /// - authenticationStorage: The storage for authentication objects. - /// - minTLSVersion: The minimum supported TLS version. - - public convenience init( - userID: UUID, - clientID: String, - backendURL: URL, - minTLSVersion: TLSVersion, - cookieEncryptionKey: Data, - keychain: any KeychainProtocol - ) { - let configFactory = URLSessionConfigurationFactory(minTLSVersion: minTLSVersion) - let configuration = configFactory.makeRESTAPISessionConfiguration() - let networkService = NetworkService(baseURL: backendURL) - let urlSession = URLSession(configuration: configuration) - networkService.configure(with: urlSession) - - let cookieStorage = CookieStorage( - userID: userID, - cookieEncryptionKey: cookieEncryptionKey, - keychain: keychain - ) - - let authenticationManager = AuthenticationManager( - clientID: clientID, - cookieStorage: cookieStorage, - networkService: networkService - ) - - self.init( - networkService: networkService, - authenticationManager: authenticationManager - ) - } - - init( + public init( networkService: NetworkService, authenticationManager: any AuthenticationManagerProtocol ) { diff --git a/WireAPI/Sources/WireAPI/Network/NetworkService/NetworkService.swift b/WireAPI/Sources/WireAPI/Network/NetworkService/NetworkService.swift index c9f8087bbec..8185ff7bbac 100644 --- a/WireAPI/Sources/WireAPI/Network/NetworkService/NetworkService.swift +++ b/WireAPI/Sources/WireAPI/Network/NetworkService/NetworkService.swift @@ -18,14 +18,16 @@ import Foundation -final class NetworkService: NSObject { +public final class NetworkService: NSObject { private let baseURL: URL + private let serverTrustValidator: ServerTrustValidator private var urlSession: URLSession? private var webSocketsByTask = [URLSessionWebSocketTask: WebSocket]() - init(baseURL: URL) { + init(baseURL: URL, serverTrustValidator: ServerTrustValidator) { self.baseURL = baseURL + self.serverTrustValidator = serverTrustValidator } deinit { @@ -105,13 +107,14 @@ extension NetworkService: URLSessionWebSocketDelegate { } -extension NetworkService: URLSessionDataDelegate { +extension NetworkService: URLSessionTaskDelegate { public func urlSession( _ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)? ) { + // NOTE: This method is not called when when using async/await APIs. if let error { print("task did complete with error: \(error)") } else { @@ -122,27 +125,26 @@ extension NetworkService: URLSessionDataDelegate { public func urlSession( _ session: URLSession, task: URLSessionTask, - didReceive challenge: URLAuthenticationChallenge, - completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void - ) { - print("task did receive challenge") - + didReceive challenge: + URLAuthenticationChallenge + ) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { let protectionSpace = challenge.protectionSpace - guard protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust else { - completionHandler(.performDefaultHandling, challenge.proposedCredential) - return - } - - guard - protectionSpace.serverTrust != nil, - true // TODO: [WPB-10450] support certificate pinning - else { - completionHandler(.cancelAuthenticationChallenge, nil) - return + if protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { + guard let trust = challenge.protectionSpace.serverTrust else { + // If this is missing it is Apple breaking its API contract so crash. + fatalError("Missing server trust") + } + + do { + try await serverTrustValidator.validate(trust: trust, host: protectionSpace.host) + return (.performDefaultHandling, challenge.proposedCredential) + } catch { + return (.cancelAuthenticationChallenge, nil) + } + } else { + return (.performDefaultHandling, challenge.proposedCredential) } - - completionHandler(.performDefaultHandling, challenge.proposedCredential) } } diff --git a/WireAPI/Sources/WireAPI/Network/PushChannel/PushChannelService.swift b/WireAPI/Sources/WireAPI/Network/PushChannel/PushChannelService.swift index eb587850af8..6a7880ad5f4 100644 --- a/WireAPI/Sources/WireAPI/Network/PushChannel/PushChannelService.swift +++ b/WireAPI/Sources/WireAPI/Network/PushChannel/PushChannelService.swift @@ -35,44 +35,19 @@ public protocol PushChannelServiceProtocol { public final class PushChannelService: PushChannelServiceProtocol { private let networkService: NetworkService - private let authenticationStorage: any AuthenticationStorage - - public convenience init( - backendWebSocketURL: URL, - authenticationStorage: any AuthenticationStorage, - minTLSVersion: TLSVersion - ) { - let factory = URLSessionConfigurationFactory(minTLSVersion: minTLSVersion) - let configuration = factory.makeWebSocketSessionConfiguration() - let networkService = NetworkService(baseURL: backendWebSocketURL) - let urlSession = URLSession( - configuration: configuration, - delegate: networkService, - delegateQueue: nil - ) - networkService.configure(with: urlSession) - - self.init( - networkService: networkService, - authenticationStorage: authenticationStorage - ) - } + private let authenticationManager: any AuthenticationManagerProtocol init( networkService: NetworkService, - authenticationStorage: any AuthenticationStorage + authenticationManager: any AuthenticationManagerProtocol ) { self.networkService = networkService - self.authenticationStorage = authenticationStorage + self.authenticationManager = authenticationManager } public func createPushChannel(_ request: URLRequest) async throws -> any PushChannelProtocol { var request = request - - guard let accessToken = await authenticationStorage.fetchAccessToken() else { - throw PushChannelServiceError.missingAccessToken - } - + let accessToken = try await authenticationManager.getValidAccessToken() request.setAccessToken(accessToken) let webSocket = try networkService.executeWebSocketRequest(request) return PushChannel(webSocket: webSocket) diff --git a/WireAPI/Sources/WireAPI/Network/URLSessionConfigurationFactory.swift b/WireAPI/Sources/WireAPI/Network/URLSessionConfigurationFactory.swift index 4a56c783490..350f4814f78 100644 --- a/WireAPI/Sources/WireAPI/Network/URLSessionConfigurationFactory.swift +++ b/WireAPI/Sources/WireAPI/Network/URLSessionConfigurationFactory.swift @@ -21,9 +21,9 @@ import Foundation struct URLSessionConfigurationFactory { let minTLSVersion: TLSVersion + let proxySettings: ProxySettings? func makeRESTAPISessionConfiguration() -> URLSessionConfiguration { - // TODO: [WPB-10447] support proxy mode let configuration = URLSessionConfiguration.ephemeral // If no data is transmitted for this amount of time for a request, it will time out. @@ -43,13 +43,22 @@ struct URLSessionConfigurationFactory { configuration.tlsMinimumSupportedProtocolVersion = minTLSVersion.secValue configuration.urlCache = nil + + if let proxySettings { + configuration.connectionProxyDictionary = proxySettings.proxyDictionary() + } + return configuration } func makeWebSocketSessionConfiguration() -> URLSessionConfiguration { - // TODO: [WPB-10447] support proxy mode let configuration = URLSessionConfiguration.ephemeral configuration.tlsMinimumSupportedProtocolVersion = minTLSVersion.secValue + + if let proxySettings { + configuration.connectionProxyDictionary = proxySettings.proxyDictionary() + } + return configuration } diff --git a/WireAPI/Sources/WireAPISupport/Sourcery/sourcery.yml b/WireAPI/Sources/WireAPISupport/Sourcery/sourcery.yml index 6a04a2891cb..5f43a95fd38 100644 --- a/WireAPI/Sources/WireAPISupport/Sourcery/sourcery.yml +++ b/WireAPI/Sources/WireAPISupport/Sourcery/sourcery.yml @@ -3,7 +3,7 @@ sources: templates: - ${TARGET_DIR}/Sourcery/AutoMockable.stencil output: - ${DERIVED_SOURCES_DIR} + ${DERIVED_SOURCES_DIR} args: - autoMockableImports: [] - autoMockableTestableImports: ["WireAPI"] + autoMockablePublicImports: ["Foundation"] + autoMockableTestableImports: ["WireAPI"] diff --git a/WireAPI/.swiftpm/WireAPI.xctestplan b/WireAPI/Tests/TestPlans/AllTests.xctestplan similarity index 62% rename from WireAPI/.swiftpm/WireAPI.xctestplan rename to WireAPI/Tests/TestPlans/AllTests.xctestplan index cc3539a4c0c..e8951419670 100644 --- a/WireAPI/.swiftpm/WireAPI.xctestplan +++ b/WireAPI/Tests/TestPlans/AllTests.xctestplan @@ -1,20 +1,22 @@ { "configurations" : [ { - "id" : "CA909C45-1D66-4C47-BBFD-53CA3AB99A82", - "name" : "Test Scheme Action", + "id" : "FDB58F5E-9DC3-4935-B919-AA295F684D19", + "name" : "Configuration 1", "options" : { } } ], "defaultOptions" : { + "language" : "en", + "region" : "DE", "testExecutionOrdering" : "random" }, "testTargets" : [ { "target" : { - "containerPath" : "container:", + "containerPath" : "container:WireAPI", "identifier" : "WireAPITests", "name" : "WireAPITests" } diff --git a/WireAPI/Tests/WireAPITests/APIs/BackendInfoAPI/BackendInfoAPITests.swift b/WireAPI/Tests/WireAPITests/APIs/BackendInfoAPI/BackendInfoAPITests.swift index d988080d621..28c12f0906a 100644 --- a/WireAPI/Tests/WireAPITests/APIs/BackendInfoAPI/BackendInfoAPITests.swift +++ b/WireAPI/Tests/WireAPITests/APIs/BackendInfoAPI/BackendInfoAPITests.swift @@ -70,6 +70,7 @@ final class BackendInfoAPITests: XCTestCase { BackendInfo( domain: "example.com", isFederationEnabled: true, + isMLSEnabled: false, supportedVersions: [.v0, .v1, .v2], developmentVersions: [] ) @@ -97,6 +98,7 @@ final class BackendInfoAPITests: XCTestCase { BackendInfo( domain: "example.com", isFederationEnabled: true, + isMLSEnabled: false, supportedVersions: [.v0, .v1, .v2], developmentVersions: [.v3] ) diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/ConversationsAPITests.swift b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/ConversationsAPITests.swift index 9b3b7f67d4f..246d0d79575 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/ConversationsAPITests.swift +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/ConversationsAPITests.swift @@ -19,29 +19,24 @@ import WireTestingPackage import XCTest @testable import WireAPI +@testable import WireAPISupport final class ConversationsAPITests: XCTestCase { - private var httpRequestSnapshotHelper: HTTPRequestSnapshotHelper! - private var apiSnapshotHelper: APISnapshotHelper! + private var apiSnapshotHelper: APIServiceSnapshotHelper! // MARK: - Setup override func setUp() { - super.setUp() + apiSnapshotHelper = APIServiceSnapshotHelper { apiService, apiVersion in + ConversationsAPIBuilder(apiService: apiService) + .makeAPI(for: apiVersion) - httpRequestSnapshotHelper = HTTPRequestSnapshotHelper() - apiSnapshotHelper = APISnapshotHelper { httpClient, apiVersion in - let builder = ConversationsAPIBuilder(httpClient: httpClient) - return builder.makeAPI(for: apiVersion) } } override func tearDown() { apiSnapshotHelper = nil - httpRequestSnapshotHelper = nil - - super.tearDown() } // MARK: - Tests @@ -63,21 +58,6 @@ final class ConversationsAPITests: XCTestCase { } } - func testGetConversationIdentifiers() async throws { - // given - let apiVersions = Set(APIVersion.allCases).subtracting([.v0]) - - // when - // then - try await apiSnapshotHelper.verifyRequest(for: apiVersions) { sut in - let pager = try await sut.getConversationIdentifiers() - - for try await _ in pager { - // trigger fetching data - } - } - } - func testGetMLSOneToOneConversationRequest() async throws { // Given @@ -94,18 +74,30 @@ final class ConversationsAPITests: XCTestCase { } } + func testGetConversationIdentifiers() async throws { + // given + let apiVersions = Set(APIVersion.allCases).subtracting([.v0]) + + // when + // then + try await apiSnapshotHelper.verifyRequest(for: apiVersions) { sut in + let pager = try await sut.getConversationIdentifiers() + + for try await _ in pager { + // trigger fetching data + } + } + } + func testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200_thenVerifyRequests() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockJSONResource( - code: .ok, - name: "testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200" - ) - ] + + let apiService = MockAPIServiceProtocol.withResponses([ + (.ok, "testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200") + ]) // when - let api = ConversationsAPIV0(httpClient: httpClient) + let api = ConversationsAPIV0(apiService: apiService) let pager = try await api.getLegacyConversationIdentifiers() for try await _ in pager { @@ -113,26 +105,23 @@ final class ConversationsAPITests: XCTestCase { } // then - for (index, request) in httpClient.receivedRequests.enumerated() { - await httpRequestSnapshotHelper.verifyRequest(request: request, resourceName: "v0.\(index)") + + try await apiSnapshotHelper.verifyRequest(for: [.v0], apiService: apiService) { api in + _ = try await api.getLegacyConversationIdentifiers() } } func testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200_thenVerifyResponse() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockJSONResource( - code: .ok, - name: "testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200" - ) - ] + let apiService = MockAPIServiceProtocol.withResponses([ + (.ok, "testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200") + ]) let expectedIDs: [UUID] = [ try XCTUnwrap(UUID(uuidString: "14c3f0ff-1a46-4e66-8845-ae084f09c483")) ] - let api = ConversationsAPIV0(httpClient: httpClient) + let api = ConversationsAPIV0(apiService: apiService) // when let pager = try await api.getLegacyConversationIdentifiers() @@ -146,12 +135,13 @@ final class ConversationsAPITests: XCTestCase { func testGetLegacyConversationIdentifiers_givenV0AndErrorResponse() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockError(code: .serviceUnavailable, label: "service unavailable") - ] - let api = ConversationsAPIV0(httpClient: httpClient) + let apiService = MockAPIServiceProtocol.withError( + statusCode: .serviceUnavailable, + label: "service unavailable" + ) + + let api = ConversationsAPIV0(apiService: apiService) // when // then @@ -169,37 +159,28 @@ final class ConversationsAPITests: XCTestCase { func testGetConversationIdentifiers_givenV1AndSuccessResponse200_thenVerifyRequests() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockJSONResource( - code: .ok, - name: "testGetConversationIdentifiers_givenV1AndSuccessResponse200" - ) - ] + let apiService = MockAPIServiceProtocol.withResponses([ + (.ok, "testGetConversationIdentifiers_givenV1AndSuccessResponse200") + ]) // when - let api = ConversationsAPIV1(httpClient: httpClient) + let api = ConversationsAPIV1(apiService: apiService) let pager = try await api.getConversationIdentifiers() for try await _ in pager { // trigger fetching date } - // then - for (index, request) in httpClient.receivedRequests.enumerated() { - await httpRequestSnapshotHelper.verifyRequest(request: request, resourceName: "v1.\(index)") + try await apiSnapshotHelper.verifyRequest(for: [.v1], apiService: apiService) { api in + _ = try await api.getConversationIdentifiers() } } func testGetConversationIdentifiers_givenV1AndSuccessResponse200_thenVerifyResponse() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockJSONResource( - code: .ok, - name: "testGetConversationIdentifiers_givenV1AndSuccessResponse200" - ) - ] + let apiService = MockAPIServiceProtocol.withResponses([ + (.ok, "testGetConversationIdentifiers_givenV1AndSuccessResponse200") + ]) let expectedIDs: [QualifiedID] = [ QualifiedID( @@ -208,7 +189,7 @@ final class ConversationsAPITests: XCTestCase { ) ] - let api = ConversationsAPIV1(httpClient: httpClient) + let api = ConversationsAPIV1(apiService: apiService) // when let pager = try await api.getConversationIdentifiers() @@ -222,12 +203,12 @@ final class ConversationsAPITests: XCTestCase { func testGetConversationIdentifiers_givenV1AndErrorResponse() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockError(code: .serviceUnavailable, label: "service unavailable") - ] + let apiService = MockAPIServiceProtocol.withError( + statusCode: .serviceUnavailable, + label: "service unavailable" + ) - let api = ConversationsAPIV1(httpClient: httpClient) + let api = ConversationsAPIV1(apiService: apiService) // when // then @@ -261,15 +242,12 @@ final class ConversationsAPITests: XCTestCase { func testGetConversations_givenV0AndSuccessResponse200_thenVerifyResponse() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockJSONResource( - code: .ok, - name: "testGetConversations_givenV0AndSuccessResponse200" - ) - ] - let api = ConversationsAPIV0(httpClient: httpClient) + let apiService = MockAPIServiceProtocol.withResponses([ + (.ok, "testGetConversations_givenV0AndSuccessResponse200") + ]) + + let api = ConversationsAPIV0(apiService: apiService) // when // then @@ -281,12 +259,12 @@ final class ConversationsAPITests: XCTestCase { func testGetConversations_givenV0AndSuccessResponse400() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockError(code: .badRequest, label: "invalid body") - ] + let apiService = MockAPIServiceProtocol.withError( + statusCode: .badRequest, + label: "invalid body" + ) - let api = ConversationsAPIV0(httpClient: httpClient) + let api = ConversationsAPIV0(apiService: apiService) // when // then @@ -302,12 +280,12 @@ final class ConversationsAPITests: XCTestCase { func testGetConversations_givenV0AndSuccessResponse503() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockError(code: .serviceUnavailable, label: "service unavailable") - ] + let apiService = MockAPIServiceProtocol.withError( + statusCode: .serviceUnavailable, + label: "service unavailable" + ) - let api = ConversationsAPIV0(httpClient: httpClient) + let api = ConversationsAPIV0(apiService: apiService) // when // then @@ -323,15 +301,12 @@ final class ConversationsAPITests: XCTestCase { func testGetConversations_givenV2AndSuccessResponse200_thenVerifyResponse() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockJSONResource( - code: .ok, - name: "testGetConversations_givenV2AndSuccessResponse200" - ) - ] - let api = ConversationsAPIV2(httpClient: httpClient) + let apiService = MockAPIServiceProtocol.withResponses([ + (.ok, "testGetConversations_givenV2AndSuccessResponse200") + ]) + + let api = ConversationsAPIV2(apiService: apiService) // when // then @@ -343,12 +318,12 @@ final class ConversationsAPITests: XCTestCase { func testGetConversations_givenV2AndSuccessResponse400() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockError(code: .badRequest, label: "invalid body") - ] + let apiService = MockAPIServiceProtocol.withError( + statusCode: .badRequest, + label: "invalid body" + ) - let api = ConversationsAPIV2(httpClient: httpClient) + let api = ConversationsAPIV2(apiService: apiService) // when // then @@ -364,12 +339,13 @@ final class ConversationsAPITests: XCTestCase { func testGetConversations_givenV2AndSuccessResponse503() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockError(code: .serviceUnavailable, label: "service unavailable") - ] - let api = ConversationsAPIV2(httpClient: httpClient) + let apiService = MockAPIServiceProtocol.withError( + statusCode: .serviceUnavailable, + label: "service unavailable" + ) + + let api = ConversationsAPIV2(apiService: apiService) // when // then @@ -385,15 +361,12 @@ final class ConversationsAPITests: XCTestCase { func testGetConversations_givenV3AndSuccessResponse200_thenVerifyResponse() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockJSONResource( - code: .ok, - name: "testGetConversations_givenV3AndSuccessResponse200" - ) - ] - let api = ConversationsAPIV3(httpClient: httpClient) + let apiService = MockAPIServiceProtocol.withResponses([ + (.ok, "testGetConversations_givenV3AndSuccessResponse200") + ]) + + let api = ConversationsAPIV3(apiService: apiService) // when // then @@ -409,12 +382,12 @@ final class ConversationsAPITests: XCTestCase { func testGetConversations_givenV3AndSuccessResponse400() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockError(code: .badRequest, label: "invalid body") - ] + let apiService = MockAPIServiceProtocol.withError( + statusCode: .badRequest, + label: "invalid body" + ) - let api = ConversationsAPIV3(httpClient: httpClient) + let api = ConversationsAPIV3(apiService: apiService) // when // then @@ -430,12 +403,12 @@ final class ConversationsAPITests: XCTestCase { func testGetConversations_givenV3AndSuccessResponse503() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockError(code: .serviceUnavailable, label: "service unavailable") - ] + let apiService = MockAPIServiceProtocol.withError( + statusCode: .serviceUnavailable, + label: "service unavailable" + ) - let api = ConversationsAPIV3(httpClient: httpClient) + let api = ConversationsAPIV3(apiService: apiService) // when // then @@ -451,15 +424,12 @@ final class ConversationsAPITests: XCTestCase { func testGetConversations_givenV5AndSuccessResponse200_thenVerifyResponse() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockJSONResource( - code: .ok, - name: "testGetConversations_givenV5AndSuccessResponse200" - ) - ] - let api = ConversationsAPIV5(httpClient: httpClient) + let apiService = MockAPIServiceProtocol.withResponses([ + (.ok, "testGetConversations_givenV5AndSuccessResponse200") + ]) + + let api = ConversationsAPIV5(apiService: apiService) // when // then @@ -475,12 +445,12 @@ final class ConversationsAPITests: XCTestCase { func testGetConversations_givenV5AndSuccessResponse503() async throws { // given - let httpClient = MockHTTPResponsesClient() - httpClient.httpResponses = [ - try HTTPResponse.mockError(code: .serviceUnavailable, label: "service unavailable") - ] + let apiService = MockAPIServiceProtocol.withError( + statusCode: .serviceUnavailable, + label: "service unavailable" + ) - let api = ConversationsAPIV5(httpClient: httpClient) + let api = ConversationsAPIV5(apiService: apiService) // when // then @@ -497,43 +467,38 @@ final class ConversationsAPITests: XCTestCase { func testGetMLSOneToOneConversation_Success_Response_V5_And_Next_Versions() async throws { // Given - let httpClient = try HTTPClientMock( - code: .ok, - payloadResourceName: "testGetMLSOneOnOneConversationV5SuccessResponse200" + let supportedVersions = APIVersion.v5.andNextVersions + + let mocks: [MockAPIServiceProtocol.Response] = Array( + repeating: (.ok, "testGetMLSOneOnOneConversationV5SuccessResponse200"), + count: supportedVersions.count ) - let supportedVersions = APIVersion.v5.andNextVersions + let apiService = MockAPIServiceProtocol.withResponses(mocks) - let suts = supportedVersions.map { $0.buildAPI(client: httpClient) } + let suts = supportedVersions.map { $0.buildAPI(apiService: apiService) } // When - try await withThrowingTaskGroup(of: Conversation.self) { taskGroup in - for sut in suts { - taskGroup.addTask { - try await sut.getMLSOneToOneConversation( - userID: Scaffolding.userID, - in: Scaffolding.domain - ) - } - } + for sut in suts { + let mlsConversation = try await sut.getMLSOneToOneConversation( + userID: Scaffolding.userID, + in: Scaffolding.domain + ) - for try await value in taskGroup { - // Then - XCTAssertEqual(value.id, Scaffolding.mlsConversationID) - } + XCTAssertEqual(mlsConversation.id, Scaffolding.mlsConversationID) } } func testGetMLSOneToOneConversation_UnsupportedVersionError_V0_to_V4() async throws { // Given - let httpClient = HTTPClientMock( - code: .ok, - payload: nil - ) + + let apiService = MockAPIServiceProtocol.withResponses([ + (.ok, "") + ]) let unsupportedVersions: [APIVersion] = [.v0, .v1, .v2, .v3, .v4] - let suts = unsupportedVersions.map { $0.buildAPI(client: httpClient) } + let suts = unsupportedVersions.map { $0.buildAPI(apiService: apiService) } try await withThrowingTaskGroup(of: Void.self) { taskGroup in for sut in suts { @@ -556,12 +521,12 @@ final class ConversationsAPITests: XCTestCase { func testGetMLSOneToOneConversation_Failure_Response_MLS_Not_Enabled() async throws { // Given - let httpClient = try HTTPClientMock( - code: .badRequest, - errorLabel: "mls-not-enabled" + let apiService = MockAPIServiceProtocol.withError( + statusCode: .badRequest, + label: "mls-not-enabled" ) - let sut = APIVersion.v5.buildAPI(client: httpClient) + let sut = APIVersion.v5.buildAPI(apiService: apiService) // Then @@ -577,12 +542,12 @@ final class ConversationsAPITests: XCTestCase { func testGetMLSOneToOneConversation_Failure_Response_Not_Connected() async throws { // Given - let httpClient = try HTTPClientMock( - code: .forbidden, - errorLabel: "not-connected" + let apiService = MockAPIServiceProtocol.withError( + statusCode: .forbidden, + label: "not-connected" ) - let sut = APIVersion.v5.buildAPI(client: httpClient) + let sut = APIVersion.v5.buildAPI(apiService: apiService) // Then @@ -598,12 +563,11 @@ final class ConversationsAPITests: XCTestCase { func testGetMLSOneToOneConversation_Failure_UserID_And_Domain_Empty() async throws { // Given - let httpClient = try HTTPClientMock( - code: .ok, - payloadResourceName: "testGetMLSOneOnOneConversationV5SuccessResponse200" - ) + let apiService = MockAPIServiceProtocol.withResponses([ + (.ok, "testGetMLSOneOnOneConversationV5SuccessResponse200") + ]) - let sut = APIVersion.v5.buildAPI(client: httpClient) + let sut = APIVersion.v5.buildAPI(apiService: apiService) // Then @@ -616,17 +580,205 @@ final class ConversationsAPITests: XCTestCase { } } + func testGetConversationGuestLink() async throws { + // Given + + let apiVersions = APIVersion.allCases + let conversationID = Scaffolding.conversationID.uuidString + + // Then + + try await apiSnapshotHelper.verifyRequest(for: apiVersions) { sut in + // When + _ = try await sut.getConversationGuestLink( + conversationID: conversationID + ) + } + } + + func testGetConversationGuestLink_Success_Response_V0_To_V3() async throws { + + let supportedVersions: [APIVersion] = [.v0, .v1, .v2, .v3] + + let mocks: [MockAPIServiceProtocol.Response] = Array( + repeating: (.ok, "testGetConversationGuestLinkV0SuccessResponse200"), + count: supportedVersions.count + ) + + let apiService = MockAPIServiceProtocol.withResponses(mocks) + + let conversationID = Scaffolding.conversationID.uuidString + let suts = supportedVersions.map { $0.buildAPI(apiService: apiService) } + + // When + + for sut in suts { + let uri = try await sut.getConversationGuestLink(conversationID: conversationID) + + XCTAssertEqual(uri, Scaffolding.guestLinkV0) + } + + } + + func testGetConversationGuestLink_Success_Response_V4_And_Next_Versions() async throws { + + let supportedVersions = APIVersion.v4.andNextVersions + + let mocks: [MockAPIServiceProtocol.Response] = Array( + repeating: (.ok, "testGetConversationGuestLinkV4SuccessResponse200"), + count: supportedVersions.count + ) + + let apiService = MockAPIServiceProtocol.withResponses(mocks) + + let conversationID = Scaffolding.conversationID.uuidString + let suts = supportedVersions.map { $0.buildAPI(apiService: apiService) } + + // When + + for sut in suts { + let uri = try await sut.getConversationGuestLink(conversationID: conversationID) + + XCTAssertEqual(uri, Scaffolding.guestLinkV4) + } + + } + + func testGetConversationGuestLinkV0_Failure_Access_Denied() async throws { + // Given + + let apiService = MockAPIServiceProtocol.withError( + statusCode: .forbidden, + label: "access-denied" + ) + + let conversationID = Scaffolding.conversationID.uuidString + + let sut = APIVersion.v0.buildAPI(apiService: apiService) + + // Then + + await XCTAssertThrowsErrorAsync(ConversationsAPIError.accessDenied) { + // When + try await sut.getConversationGuestLink(conversationID: conversationID) + } + } + + func testGetConversationGuestLinkV0_Failure_Invalid_Conversation_ID() async throws { + // Given + + let apiService = MockAPIServiceProtocol.withError( + statusCode: .notFound, + label: "cnv" + ) + + let conversationID = Scaffolding.conversationID.uuidString + + let sut = APIVersion.v0.buildAPI(apiService: apiService) + + // Then + + await XCTAssertThrowsErrorAsync(ConversationsAPIError.invalidConversationID) { + // When + try await sut.getConversationGuestLink(conversationID: conversationID) + } + } + + func testGetConversationGuestLinkV0_Failure_No_Conversation_Found() async throws { + // Given + + let apiService = MockAPIServiceProtocol.withError( + statusCode: .notFound, + label: "no-conversation" + ) + + let conversationID = Scaffolding.conversationID.uuidString + + let sut = APIVersion.v0.buildAPI(apiService: apiService) + + // Then + + await XCTAssertThrowsErrorAsync(ConversationsAPIError.conversationNotFound) { + // When + try await sut.getConversationGuestLink(conversationID: conversationID) + } + } + + func testGetConversationGuestLinkV0_Failure_No_Conversation_Code_Found() async throws { + // Given + + let apiService = MockAPIServiceProtocol.withError( + statusCode: .notFound, + label: "no-conversation-code" + ) + + let conversationID = Scaffolding.conversationID.uuidString + + let sut = APIVersion.v0.buildAPI(apiService: apiService) + + // Then + + await XCTAssertThrowsErrorAsync(ConversationsAPIError.conversationCodeNotFound) { + // When + try await sut.getConversationGuestLink(conversationID: conversationID) + } + } + + func testGetConversationGuestLinkV0_Failure_Guest_Links_Disabled() async throws { + // Given + + let apiService = MockAPIServiceProtocol.withError( + statusCode: .conflict, + label: "guest-links-disabled" + ) + + let conversationID = Scaffolding.conversationID.uuidString + + let sut = APIVersion.v0.buildAPI(apiService: apiService) + + // Then + + await XCTAssertThrowsErrorAsync(ConversationsAPIError.guestLinksDisabled) { + // When + try await sut.getConversationGuestLink(conversationID: conversationID) + } + } + + func testGetConversationGuestLinkV4_Invalid_Conversation_ID() async throws { + // Given + + // Dedicated error code in V4 + let apiService = MockAPIServiceProtocol.withError( + statusCode: .badRequest, + label: "cnv" + ) + + let conversationID = Scaffolding.conversationID.uuidString + + let sut = APIVersion.v4.buildAPI(apiService: apiService) + + // Then + + await XCTAssertThrowsErrorAsync(ConversationsAPIError.invalidConversationID) { + // When + try await sut.getConversationGuestLink(conversationID: conversationID) + } + } + private enum Scaffolding { static let userID = "99db9768-04e3-4b5d-9268-831b6a25c4ab" static let domain = "domain.com" static let mlsConversationID = UUID(uuidString: "99db9768-04e3-4b5d-9268-831b6a25c4ab")! + static let conversationID = UUID.mockID1 + static let guestLinkV0 = "https://exampleV0.com" + static let guestLinkV4 = "https://exampleV4.com" } } private extension APIVersion { - func buildAPI(client: any HTTPClient) -> any ConversationsAPI { - let builder = ConversationsAPIBuilder(httpClient: client) + func buildAPI(apiService: any APIServiceProtocol) -> any ConversationsAPI { + let builder = ConversationsAPIBuilder(apiService: apiService) return builder.makeAPI(for: self) } } diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/Resources/testGetConversationGuestLinkV0SuccessResponse200.json b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/Resources/testGetConversationGuestLinkV0SuccessResponse200.json new file mode 100644 index 00000000000..36d1dfee30d --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/Resources/testGetConversationGuestLinkV0SuccessResponse200.json @@ -0,0 +1,5 @@ +{ + "code": "aGVsbG8", + "key": "aGVsbG8", + "uri": "https://exampleV0.com" +} diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/Resources/testGetConversationGuestLinkV4SuccessResponse200.json b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/Resources/testGetConversationGuestLinkV4SuccessResponse200.json new file mode 100644 index 00000000000..49f1cd04fed --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/Resources/testGetConversationGuestLinkV4SuccessResponse200.json @@ -0,0 +1,6 @@ +{ + "code": "stringstringstringst", + "has_password": true, + "key": "stringstringstringst", + "uri": "https://exampleV4.com" +} diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v0.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v0.txt new file mode 100644 index 00000000000..dab05b25a68 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v0.txt @@ -0,0 +1,2 @@ +curl \ + "/conversations/213248A1-5499-418F-8173-5010D1C1E506/code" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v1.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v1.txt new file mode 100644 index 00000000000..a7e02a85898 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v1.txt @@ -0,0 +1,2 @@ +curl \ + "/v1/conversations/213248A1-5499-418F-8173-5010D1C1E506/code" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v2.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v2.txt new file mode 100644 index 00000000000..f46d99c4e09 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v2.txt @@ -0,0 +1,2 @@ +curl \ + "/v2/conversations/213248A1-5499-418F-8173-5010D1C1E506/code" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v3.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v3.txt new file mode 100644 index 00000000000..69be2f98858 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v3.txt @@ -0,0 +1,2 @@ +curl \ + "/v3/conversations/213248A1-5499-418F-8173-5010D1C1E506/code" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v4.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v4.txt new file mode 100644 index 00000000000..dfd0c5df539 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v4.txt @@ -0,0 +1,2 @@ +curl \ + "/v4/conversations/213248A1-5499-418F-8173-5010D1C1E506/code" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v5.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v5.txt new file mode 100644 index 00000000000..7ddc0969688 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v5.txt @@ -0,0 +1,2 @@ +curl \ + "/v5/conversations/213248A1-5499-418F-8173-5010D1C1E506/code" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v6.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v6.txt new file mode 100644 index 00000000000..bfbc0d59833 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationGuestLink.request-0-v6.txt @@ -0,0 +1,2 @@ +curl \ + "/v6/conversations/213248A1-5499-418F-8173-5010D1C1E506/code" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v1.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v1.txt index 4a1e94b1d7f..353853b8a98 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v1.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v1.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v1/conversations/list-ids/" - - method: post - - body: {"size":500} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"size\":500}" \ + "/v1/conversations/list-ids/" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v2.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v2.txt index ce9cbc1ab00..26f328ac638 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v2.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v2.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v2/conversations/list-ids/" - - method: post - - body: {"size":500} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"size\":500}" \ + "/v2/conversations/list-ids/" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v3.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v3.txt index 1a79004b08d..fb557932e8e 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v3.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v3.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v3/conversations/list-ids/" - - method: post - - body: {"size":500} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"size\":500}" \ + "/v3/conversations/list-ids/" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v4.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v4.txt index 4feb72c1b45..3213151b6ad 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v4.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v4.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v4/conversations/list-ids/" - - method: post - - body: {"size":500} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"size\":500}" \ + "/v4/conversations/list-ids/" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v5.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v5.txt index 7e6844fa519..ff6c7bb768b 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v5.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v5.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v5/conversations/list-ids/" - - method: post - - body: {"size":500} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"size\":500}" \ + "/v5/conversations/list-ids/" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v6.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v6.txt index c6634bbe40d..535e14a4577 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v6.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers.request-0-v6.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v6/conversations/list-ids/" - - method: post - - body: {"size":500} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"size\":500}" \ + "/v6/conversations/list-ids/" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers_givenV1AndSuccessResponse200_thenVerifyRequests.request-0-v1.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers_givenV1AndSuccessResponse200_thenVerifyRequests.request-0-v1.txt new file mode 100644 index 00000000000..353853b8a98 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers_givenV1AndSuccessResponse200_thenVerifyRequests.request-0-v1.txt @@ -0,0 +1,5 @@ +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"size\":500}" \ + "/v1/conversations/list-ids/" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers_givenV1AndSuccessResponse200_thenVerifyRequests.v1-0.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers_givenV1AndSuccessResponse200_thenVerifyRequests.v1-0.txt deleted file mode 100644 index 4a1e94b1d7f..00000000000 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversationIdentifiers_givenV1AndSuccessResponse200_thenVerifyRequests.v1-0.txt +++ /dev/null @@ -1,4 +0,0 @@ -- HTTPRequest - - path: "/v1/conversations/list-ids/" - - method: post - - body: {"size":500} diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v0.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v0.txt index 973422a6b95..fb139cd66ba 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v0.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v0.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/conversations/list/v2" - - method: post - - body: {"qualified_ids":[{"domain":"wire.com","id":"213248A1-5499-418F-8173-5010D1C1E506"}]} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"qualified_ids\":[{\"domain\":\"wire.com\",\"id\":\"213248A1-5499-418F-8173-5010D1C1E506\"}]}" \ + "/conversations/list/v2" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v1.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v1.txt index eb807b4ee02..59adca7974f 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v1.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v1.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v1/conversations/list/v2" - - method: post - - body: {"qualified_ids":[{"domain":"wire.com","id":"213248A1-5499-418F-8173-5010D1C1E506"}]} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"qualified_ids\":[{\"domain\":\"wire.com\",\"id\":\"213248A1-5499-418F-8173-5010D1C1E506\"}]}" \ + "/v1/conversations/list/v2" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v2.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v2.txt index efef9c29192..3edfa87f1d0 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v2.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v2.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v2/conversations/list" - - method: post - - body: {"qualified_ids":[{"domain":"wire.com","id":"213248A1-5499-418F-8173-5010D1C1E506"}]} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"qualified_ids\":[{\"domain\":\"wire.com\",\"id\":\"213248A1-5499-418F-8173-5010D1C1E506\"}]}" \ + "/v2/conversations/list" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v3.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v3.txt index f8b4d21934f..ce623e2c087 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v3.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v3.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v3/conversations/list" - - method: post - - body: {"qualified_ids":[{"domain":"wire.com","id":"213248A1-5499-418F-8173-5010D1C1E506"}]} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"qualified_ids\":[{\"domain\":\"wire.com\",\"id\":\"213248A1-5499-418F-8173-5010D1C1E506\"}]}" \ + "/v3/conversations/list" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v4.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v4.txt index 4cc4e4c9a03..196705eb19f 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v4.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v4.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v4/conversations/list" - - method: post - - body: {"qualified_ids":[{"domain":"wire.com","id":"213248A1-5499-418F-8173-5010D1C1E506"}]} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"qualified_ids\":[{\"domain\":\"wire.com\",\"id\":\"213248A1-5499-418F-8173-5010D1C1E506\"}]}" \ + "/v4/conversations/list" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v5.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v5.txt index d62f398138a..b937777d2ab 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v5.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v5.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v5/conversations/list" - - method: post - - body: {"qualified_ids":[{"domain":"wire.com","id":"213248A1-5499-418F-8173-5010D1C1E506"}]} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"qualified_ids\":[{\"domain\":\"wire.com\",\"id\":\"213248A1-5499-418F-8173-5010D1C1E506\"}]}" \ + "/v5/conversations/list" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v6.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v6.txt index c770cdf5dcb..39969324dc8 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v6.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetConversations_givenAllAPIVersions_thenVerifyRequests.request-0-v6.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/v6/conversations/list" - - method: post - - body: {"qualified_ids":[{"domain":"wire.com","id":"213248A1-5499-418F-8173-5010D1C1E506"}]} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"qualified_ids\":[{\"domain\":\"wire.com\",\"id\":\"213248A1-5499-418F-8173-5010D1C1E506\"}]}" \ + "/v6/conversations/list" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetLegacyConversationIdentifiers.request-0-v0.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetLegacyConversationIdentifiers.request-0-v0.txt index 28b0bcc81c1..e2a62efa409 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetLegacyConversationIdentifiers.request-0-v0.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetLegacyConversationIdentifiers.request-0-v0.txt @@ -1,4 +1,5 @@ -- HTTPRequest - - path: "/conversations/list-ids/" - - method: post - - body: {"size":500} +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"size\":500}" \ + "/conversations/list-ids/" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200_thenVerifyRequests.request-0-v0.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200_thenVerifyRequests.request-0-v0.txt new file mode 100644 index 00000000000..e2a62efa409 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200_thenVerifyRequests.request-0-v0.txt @@ -0,0 +1,5 @@ +curl \ + --request POST \ + --header "Content-Type: application/json" \ + --data "{\"size\":500}" \ + "/conversations/list-ids/" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200_thenVerifyRequests.v0-0.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200_thenVerifyRequests.v0-0.txt deleted file mode 100644 index 28b0bcc81c1..00000000000 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetLegacyConversationIdentifiers_givenV0AndSuccessResponse200_thenVerifyRequests.v0-0.txt +++ /dev/null @@ -1,4 +0,0 @@ -- HTTPRequest - - path: "/conversations/list-ids/" - - method: post - - body: {"size":500} diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetMLSOneToOneConversationRequest.request-0-v5.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetMLSOneToOneConversationRequest.request-0-v5.txt index db3bc27a409..87649538310 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetMLSOneToOneConversationRequest.request-0-v5.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetMLSOneToOneConversationRequest.request-0-v5.txt @@ -1,4 +1,2 @@ -- HTTPRequest - - path: "/v5/conversations/one2one/domain.com/99db9768-04e3-4b5d-9268-831b6a25c4ab" - - method: get - - body: none +curl \ + "/v5/conversations/one2one/domain.com/99db9768-04e3-4b5d-9268-831b6a25c4ab" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetMLSOneToOneConversationRequest.request-0-v6.txt b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetMLSOneToOneConversationRequest.request-0-v6.txt index 8fbe78be92b..1aea3e5d2ac 100644 --- a/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetMLSOneToOneConversationRequest.request-0-v6.txt +++ b/WireAPI/Tests/WireAPITests/APIs/ConversationsAPI/__Snapshots__/ConversationsAPITests/testGetMLSOneToOneConversationRequest.request-0-v6.txt @@ -1,4 +1,2 @@ -- HTTPRequest - - path: "/v6/conversations/one2one/domain.com/99db9768-04e3-4b5d-9268-831b6a25c4ab" - - method: get - - body: none +curl \ + "/v6/conversations/one2one/domain.com/99db9768-04e3-4b5d-9268-831b6a25c4ab" \ No newline at end of file diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/TeamsAPITests.swift b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/TeamsAPITests.swift index 41552dab264..0517d6cfea3 100644 --- a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/TeamsAPITests.swift +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/TeamsAPITests.swift @@ -59,9 +59,9 @@ final class TeamsAPITests: XCTestCase { } } - func testGetLegalholdStatusRequest() async throws { + func testGetLegalholdInfoRequest() async throws { try await apiSnapshotHelper.verifyRequestForAllAPIVersions { sut in - _ = try await sut.getLegalholdStatus(for: .mockID1, userID: .mockID2) + _ = try await sut.getLegalholdInfo(for: .mockID1, userID: .mockID2) } } @@ -252,13 +252,17 @@ final class TeamsAPITests: XCTestCase { } } - func testGetLegalholdStatus_SuccessResponse_200_V0() async throws { + func testGetLegalholdInfo_SuccessResponse_200_V0() async throws { // Given let httpClient = try HTTPClientMock( code: .ok, jsonResponse: """ { - "status": "pending" + "status": "pending", + "last_prekey": { + "id": 12345, + "key": "foo" + } } """ ) @@ -266,43 +270,33 @@ final class TeamsAPITests: XCTestCase { let sut = TeamsAPIV0(httpClient: httpClient) // When - let result = try await sut.getLegalholdStatus( + let result = try await sut.getLegalholdInfo( for: Team.ID(), userID: UUID() ) // Then - XCTAssertEqual(result, .pending) + let expectedPrekey = LegalholdPrekey(id: 12_345, base64EncodedKey: "foo") + XCTAssertEqual(result.status, .pending) + XCTAssertEqual(result.prekey, expectedPrekey) } - func testGetLegalholdStatus_FailureResponse_InvalidRequest_V0() async throws { - // Given - let httpClient = try HTTPClientMock(code: .notFound, errorLabel: "") - let sut = TeamsAPIV0(httpClient: httpClient) - - // Then - await XCTAssertThrowsErrorAsync(TeamsAPIError.invalidRequest) { - // When - try await sut.getLegalholdStatus( - for: Team.ID(), - userID: UUID() - ) - } + func testGetLegalholdInfo_FailureResponse_InvalidRequest_V0() async throws { + try await internalTest_GetLegalholdInfo_Failure( + expectedError: TeamsAPIError.invalidRequest, + for: .v0, + code: .notFound, + errorLabel: "" + ) } - func testGetLegalholdStatus_FailureResponse_MemberNotFound_V0() async throws { - // Given - let httpClient = try HTTPClientMock(code: .notFound, errorLabel: "no-team-member") - let sut = TeamsAPIV0(httpClient: httpClient) - - // Then - await XCTAssertThrowsErrorAsync(TeamsAPIError.teamMemberNotFound) { - // When - try await sut.getLegalholdStatus( - for: Team.ID(), - userID: UUID() - ) - } + func testGetLegalholdInfo_FailureResponse_MemberNotFound_V0() async throws { + try await internalTest_GetLegalholdInfo_Failure( + expectedError: TeamsAPIError.teamMemberNotFound, + for: .v0, + code: .notFound, + errorLabel: "no-team-member" + ) } // MARK: - V2 @@ -375,19 +369,13 @@ final class TeamsAPITests: XCTestCase { } } - func testGetLegalholdStatus_FailureResponse_InvalidRequest_V4() async throws { - // Given - let httpClient = try HTTPClientMock(code: .badRequest, errorLabel: "") - let sut = TeamsAPIV4(httpClient: httpClient) - - // Then - await XCTAssertThrowsErrorAsync(TeamsAPIError.invalidRequest) { - // When - try await sut.getLegalholdStatus( - for: Team.ID(), - userID: UUID() - ) - } + func testGetLegalholdInfo_FailureResponse_InvalidRequest_V4() async throws { + try await internalTest_GetLegalholdInfo_Failure( + expectedError: TeamsAPIError.invalidRequest, + for: .v4, + code: .badRequest, + errorLabel: "" + ) } // MARK: - V5 @@ -404,15 +392,32 @@ final class TeamsAPITests: XCTestCase { } } - func testGetLegalholdStatus_FailureResponse_InvalidRequest_V5() async throws { + func testGetLegalholdInfo_FailureResponse_InvalidRequest_V5() async throws { + try await internalTest_GetLegalholdInfo_Failure( + expectedError: TeamsAPIError.invalidRequest, + for: .v5, + code: .notFound, + errorLabel: "" + ) + } + + private func internalTest_GetLegalholdInfo_Failure( + expectedError: any Error & Equatable, + for apiVersion: APIVersion, + code: HTTPStatusCode, + errorLabel: String, + file: StaticString = #file, + line: UInt = #line + ) async throws { // Given - let httpClient = try HTTPClientMock(code: .notFound, errorLabel: "") - let sut = TeamsAPIV5(httpClient: httpClient) + let httpClient = try HTTPClientMock(code: code, errorLabel: errorLabel) + let sut = apiVersion.buildAPI(client: httpClient) // Then - await XCTAssertThrowsErrorAsync(TeamsAPIError.invalidRequest) { + + await XCTAssertThrowsErrorAsync(expectedError) { // When - try await sut.getLegalholdStatus( + try await sut.getLegalholdInfo( for: Team.ID(), userID: UUID() ) @@ -420,3 +425,10 @@ final class TeamsAPITests: XCTestCase { } } + +private extension APIVersion { + func buildAPI(client: any HTTPClient) -> any TeamsAPI { + let builder = TeamsAPIBuilder(httpClient: client) + return builder.makeAPI(for: self) + } +} diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v0.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v0.txt new file mode 100644 index 00000000000..f08e8e90296 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v0.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v1.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v1.txt new file mode 100644 index 00000000000..c2629df16cf --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v1.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v1/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v2.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v2.txt new file mode 100644 index 00000000000..03509c80054 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v2.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v2/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v3.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v3.txt new file mode 100644 index 00000000000..f0a8a91c317 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v3.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v3/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v4.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v4.txt new file mode 100644 index 00000000000..df69c86a56a --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v4.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v4/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v5.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v5.txt new file mode 100644 index 00000000000..bb7d083563a --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v5.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v5/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v6.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v6.txt new file mode 100644 index 00000000000..7dca81d74cb --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdInfoRequest.request-0-v6.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v6/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v0.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v0.txt new file mode 100644 index 00000000000..f08e8e90296 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v0.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v1.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v1.txt new file mode 100644 index 00000000000..c2629df16cf --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v1.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v1/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v2.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v2.txt new file mode 100644 index 00000000000..03509c80054 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v2.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v2/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v3.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v3.txt new file mode 100644 index 00000000000..f0a8a91c317 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v3.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v3/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v4.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v4.txt new file mode 100644 index 00000000000..df69c86a56a --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v4.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v4/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v5.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v5.txt new file mode 100644 index 00000000000..bb7d083563a --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v5.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v5/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v6.txt b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v6.txt new file mode 100644 index 00000000000..7dca81d74cb --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/TeamsAPI/__Snapshots__/TeamsAPITests/testGetLegalholdRequest.request-0-v6.txt @@ -0,0 +1,4 @@ +- HTTPRequest + - path: "/v6/teams/213248a1-5499-418f-8173-5010d1c1e506/legalhold/302c59b0-037c-4b0f-a3ed-ccdbfb4cfe2c" + - method: get + - body: none diff --git a/WireAPI/Tests/WireAPITests/APIs/UpdateEventsAPI/UpdateEventsAPITests.swift b/WireAPI/Tests/WireAPITests/APIs/UpdateEventsAPI/UpdateEventsAPITests.swift index 06883db00ce..b083870cc58 100644 --- a/WireAPI/Tests/WireAPITests/APIs/UpdateEventsAPI/UpdateEventsAPITests.swift +++ b/WireAPI/Tests/WireAPITests/APIs/UpdateEventsAPI/UpdateEventsAPITests.swift @@ -39,20 +39,20 @@ final class UpdateEventsAPITests: XCTestCase { func testGetUpdateEvents() async throws { // Then - try await createSnapshotter().verifyRequestForAllAPIVersions { + try await createSnapshotter().verifyRequestForAllAPIVersions( // Given - .withResponses([ + apiService: .withResponses([ (.ok, "GetUpdateEventsSuccessResponse200_Page1"), (.ok, "GetUpdateEventsSuccessResponse200_Page2") - ]) - } when: { sut in - for try await _ in sut.getUpdateEvents( - selfClientID: Scaffolding.selfClientID, - sinceEventID: Scaffolding.lastUpdateEventID - ) { - // Nothing to assert here since we're only snapshotting request. + ]), when: { sut in + for try await _ in sut.getUpdateEvents( + selfClientID: Scaffolding.selfClientID, + sinceEventID: Scaffolding.lastUpdateEventID + ) { + // Nothing to assert here since we're only snapshotting request. + } } - } + ) } // MARK: - Response handling diff --git a/WireAPI/Tests/WireAPITests/APIs/UserClientsAPI/Resources/GetClientsSuccessResponseV0.json b/WireAPI/Tests/WireAPITests/APIs/UserClientsAPI/Resources/GetOtherUserClientsSuccessResponseV0.json similarity index 100% rename from WireAPI/Tests/WireAPITests/APIs/UserClientsAPI/Resources/GetClientsSuccessResponseV0.json rename to WireAPI/Tests/WireAPITests/APIs/UserClientsAPI/Resources/GetOtherUserClientsSuccessResponseV0.json diff --git a/WireAPI/Tests/WireAPITests/APIs/UserClientsAPI/Resources/GetSelfClientsSuccessResponseV7.json b/WireAPI/Tests/WireAPITests/APIs/UserClientsAPI/Resources/GetSelfClientsSuccessResponseV7.json new file mode 100644 index 00000000000..2d111b801a1 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/APIs/UserClientsAPI/Resources/GetSelfClientsSuccessResponseV7.json @@ -0,0 +1,40 @@ +[ + { + "capabilities": [ + "legalhold-implicit-consent" + ], + "class": "phone", + "cookie": "string", + "id": "string", + "label": "string", + "location": { + "lat": 0, + "lon": 0 + }, + "mls_public_keys": { + "ed25519": "ZXhhbXBsZQo=" + }, + "model": "string", + "time": "2021-05-12T10:52:02.671Z", + "type": "temporary" + }, + { + "capabilities": [ + "legalhold-implicit-consent" + ], + "class": "phone", + "cookie": "string", + "id": "string", + "label": "string", + "location": { + "lat": 0, + "lon": 0 + }, + "mls_public_keys": { + "ed25519": "ZXhhbXBsZQo=" + }, + "model": "string", + "time": "2021-05-12T10:52:02.671Z", + "type": "temporary" + } +] diff --git a/WireAPI/Tests/WireAPITests/APIs/UserClientsAPI/UserClientsAPITests.swift b/WireAPI/Tests/WireAPITests/APIs/UserClientsAPI/UserClientsAPITests.swift index 683d7f06581..a62712a70a4 100644 --- a/WireAPI/Tests/WireAPITests/APIs/UserClientsAPI/UserClientsAPITests.swift +++ b/WireAPI/Tests/WireAPITests/APIs/UserClientsAPI/UserClientsAPITests.swift @@ -51,9 +51,9 @@ final class UserClientsAPITests: XCTestCase { // MARK: - V0 - func testGetSelfUserClients_SuccessResponse_200_V0_And_Next_Versions() async throws { + func testGetSelfUserClients_SuccessResponse_200_V0_to_V6() async throws { try await withThrowingTaskGroup(of: [SelfUserClient].self) { taskGroup in - let testedVersions = APIVersion.v0.andNextVersions + let testedVersions = [APIVersion.v0, .v1, .v2, .v3, .v4, .v5, .v6] for version in testedVersions { // Given @@ -85,7 +85,7 @@ final class UserClientsAPITests: XCTestCase { for version in testedVersions { // Given let apiService = MockAPIServiceProtocol.withResponses([ - (.ok, "GetClientsSuccessResponseV0") + (.ok, "GetOtherUserClientsSuccessResponseV0") ]) let sut = version.buildAPI(apiService: apiService) @@ -106,6 +106,37 @@ final class UserClientsAPITests: XCTestCase { } } + // MARK: - V7 + + func testGetSelfUserClients_SuccessResponse_200_V7_And_Next_Versions() async throws { + try await withThrowingTaskGroup(of: [SelfUserClient].self) { taskGroup in + let testedVersions = APIVersion.v7.andNextVersions + + for version in testedVersions { + // Given + let apiService = MockAPIServiceProtocol.withResponses([ + (.ok, "GetSelfClientsSuccessResponseV7") + ]) + + let sut = version.buildAPI(apiService: apiService) + + taskGroup.addTask { + // When + try await sut.getSelfClients() + } + + for try await value in taskGroup { + for item in value { + // Then + XCTAssertEqual(item, Scaffolding.userClient) + } + } + } + } + } + + // MARK: - + enum Scaffolding { static let userClient = SelfUserClient( id: "string", diff --git a/WireAPI/Tests/WireAPITests/Authentication/AuthenticationManagerTests.swift b/WireAPI/Tests/WireAPITests/Authentication/AuthenticationManagerTests.swift index 84b244b7bcf..6149bb800e6 100644 --- a/WireAPI/Tests/WireAPITests/Authentication/AuthenticationManagerTests.swift +++ b/WireAPI/Tests/WireAPITests/Authentication/AuthenticationManagerTests.swift @@ -30,7 +30,10 @@ final class AuthenticationManagerTests: XCTestCase { override func setUpWithError() throws { cookieStorage = MockCookieStorageProtocol() backendURL = try XCTUnwrap(URL(string: "https://www.example.com")) - let networkService = NetworkService(baseURL: backendURL) + let networkService = NetworkService( + baseURL: backendURL, + serverTrustValidator: ServerTrustValidator(pinnedKeys: []) + ) networkService.configure(with: .mockURLSession()) sut = AuthenticationManager( diff --git a/WireAPI/Tests/WireAPITests/Backend/PinnedKeyTests.swift b/WireAPI/Tests/WireAPITests/Backend/PinnedKeyTests.swift new file mode 100644 index 00000000000..640df70e0d6 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/Backend/PinnedKeyTests.swift @@ -0,0 +1,56 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import XCTest + +@testable import WireAPI + +final class PinnedKeyTests: XCTestCase { + + func testInit_withInvalidKeyData() { + // GIVEN, WHEN, THEN + XCTAssertThrowsError( + try PinnedKey( + key: Data(), + hosts: [.equals("foo.example.com")] + ) + ) { error in + XCTAssertEqual(error as? PinnedKey.Failure, .invalidKeyData) + } + } + + func testMatchesHost() throws { + // GIVEN + let sut = try PinnedKey( + key: try PublicKeys.wire, + hosts: [ + .equals("foo.example.com"), + .equals("bar.example.com"), + .endsWith("example.net") + ] + ) + + // WHEN, THEN + XCTAssertTrue(sut.matches(host: "bar.example.com")) + XCTAssertFalse(sut.matches(host: "something.bar.example.com")) + XCTAssertTrue(sut.matches(host: "example.net")) + XCTAssertTrue(sut.matches(host: "something.example.net")) + XCTAssertFalse(sut.matches(host: "example.net.something")) + } + +} diff --git a/WireAPI/Tests/WireAPITests/Backend/ProxySettingsTests.swift b/WireAPI/Tests/WireAPITests/Backend/ProxySettingsTests.swift new file mode 100644 index 00000000000..d7646960e1d --- /dev/null +++ b/WireAPI/Tests/WireAPITests/Backend/ProxySettingsTests.swift @@ -0,0 +1,59 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import XCTest + +@testable import WireAPI + +final class ProxySettingsTests: XCTestCase { + + func testProxyDictionary_whenUnauthenticated() throws { + // GIVEN + let sut = ProxySettings.unauthenticated(host: "Host", port: 10) + + // WHEN + let result = sut.proxyDictionary() + + // THEN + XCTAssertEqual(result.count, 5) + XCTAssertEqual(result["SOCKSEnable"] as? Int, 1) + XCTAssertEqual(result["SOCKSProxy"] as? String, "Host") + XCTAssertEqual(result["SOCKSPort"] as? Int, 10) + XCTAssertEqual(result[kCFProxyTypeKey] as? String, kCFProxyTypeSOCKS as String) + XCTAssertEqual(result[kCFStreamPropertySOCKSVersion] as? String, kCFStreamSocketSOCKSVersion5 as String) + } + + func testProxyDictionary_whenAuthenticated() throws { + // GIVEN + let sut = ProxySettings.authenticated(host: "Host", port: 10, username: "User", password: "Password") + + // WHEN + let result = sut.proxyDictionary() + + // THEN + XCTAssertEqual(result.count, 7) + XCTAssertEqual(result["SOCKSEnable"] as? Int, 1) + XCTAssertEqual(result["SOCKSProxy"] as? String, "Host") + XCTAssertEqual(result["SOCKSPort"] as? Int, 10) + XCTAssertEqual(result[kCFProxyTypeKey] as? String, kCFProxyTypeSOCKS as String) + XCTAssertEqual(result[kCFStreamPropertySOCKSVersion] as? String, kCFStreamSocketSOCKSVersion5 as String) + XCTAssertEqual(result[kCFStreamPropertySOCKSUser] as? String, "User") + XCTAssertEqual(result[kCFStreamPropertySOCKSPassword] as? String, "Password") + } + +} diff --git a/WireAPI/Tests/WireAPITests/Backend/Resources/certificates.json b/WireAPI/Tests/WireAPITests/Backend/Resources/certificates.json new file mode 100644 index 00000000000..0e8e10ff802 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/Backend/Resources/certificates.json @@ -0,0 +1,15 @@ + +{ + "wire" : [ + "MIIGwjCCBaqgAwIBAgIQBWiPdednYef/Bnn3xT49PTANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTMwMQYDVQQDEypEaWdpQ2VydCBHbG9iYWwgRzIgVExTIFJTQSBTSEEyNTYgMjAyMCBDQTEwHhcNMjQwMzE4MDAwMDAwWhcNMjUwNDA5MjM1OTU5WjBKMQswCQYDVQQGEwJDSDEMMAoGA1UEBxMDWnVnMRgwFgYDVQQKEw9XaXJlIFN3aXNzIEdtYkgxEzARBgNVBAMMCioud2lyZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt5jMFa6+dUph+A01fd1WNSeohW2XhepCcJxjqb+xYzXlNMrRuj0UqczE0A+0PMHpWJG+lmwoR59fymLXklyzi5mK5nzUhJXVurG2myMnnpiN6Z730NxrlyTfmlOFi4rqNny8bqkmJj2ZFj2cZp2J3ipYvu7AB6gifHaY4zsd6kIKHY05d34SNDiwGx+Bv6RatxVCYHO8sc9QOjKSb+b4G8vZ4nWeM82Iz8ah5duYhbVYzeJ+5xgmgP2D5Xk18d8A2tW7bDhhwsNp3QLzk1vxTWyAU2SuA6rOF3/XEeiTW47KOh4tMgcdiSvK9sESZ2Xq/5/YnUQzT4WP2+x4jZNitAgMBAAGjggOTMIIDjzAfBgNVHSMEGDAWgBR0hYDAZsffN97PvSk3qgMdvu3NFzAdBgNVHQ4EFgQU/0iA8JzB4tDtwNB/3NyYfCtfTZwwHwYDVR0RBBgwFoIKKi53aXJlLmNvbYIId2lyZS5jb20wPgYDVR0gBDcwNTAzBgZngQwBAgIwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgZ8GA1UdHwSBlzCBlDBIoEagRIZCaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsRzJUTFNSU0FTSEEyNTYyMDIwQ0ExLTEuY3JsMEigRqBEhkJodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxHMlRMU1JTQVNIQTI1NjIwMjBDQTEtMS5jcmwwgYcGCCsGAQUFBwEBBHsweTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFEGCCsGAQUFBzAChkVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxHMlRMU1JTQVNIQTI1NjIwMjBDQTEtMS5jcnQwDAYDVR0TAQH/BAIwADCCAYEGCisGAQQB1nkCBAIEggFxBIIBbQFrAHcAzxFW7tUufK/zh1vZaS6b6RpxZ0qwF+ysAdJbd87MOwgAAAGOUfIjXAAABAMASDBGAiEA7VZPfsWpAqcB5BQC35MaVEwImPGzfCtPRVev3rIvrr4CIQCf37utuWmC0LTwDDMwBBW0S2eOipS9GwN19TmLkMJmQAB3AH1ZHhLheCp7HGFnfF79+NCHXBSgTpWeuQMv2Q6MLnm4AAABjlHyI0sAAAQDAEgwRgIhANT+RYxLTrc2ikPY1eArpA1A1Qecez17aBhVpG5QH6a4AiEA1Kvl1cYjSNG6Pyx46VAhaa/i8Vla2iqNDdFTrxc0H4oAdwDm0jFjQHeMwRBBBtdxuc7B0kD2loSG+7qHMh39HjeOUAAAAY5R8iNsAAAEAwBIMEYCIQD3ZzpWw6NPfncsNvh9xEr9pKGfHexeqOAqAzWtVUAViAIhAOF3ncMWaqjL5r1sy/wmWWmCez8HdUzmvO3UB5naH7a8MA0GCSqGSIb3DQEBCwUAA4IBAQAZ1G6F6nXrwhNohiKJIuS5uD5eOXXCkcKvq3HP5ydRIudX6CNMa88nMd3STJcWuJZz557Aioec/ETyCarXKPyLvRrJLnhnCO4KCJfVko6zkki0VHC7el5PfqN+7rRGavteQz3IP2nmbZciW2OoaF2fp4l9Q6IDixl5PUHVPs0nU2gCYdAgoFGGXZk9La5NiHThO92B/KHCvtuXZixOQ+sgFW1g0guV81T45qcP0wAxDDZx1twhM/EBqW6eadcmLq1AWQ8oebFcDysZRHKJ4xoEyR/EU9wrv22WonS7GfylR9ZrwxUx8d2fLaImbIHDTo5oxqPcp/WgpVR8VJrprEmX", + "MIIEyDCCA7CgAwIBAgIQDPW9BitWAvR6uFAsI8zwZjANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0yMTAzMzAwMDAwMDBaFw0zMTAzMjkyMzU5NTlaMFkxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2JhbCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEVcNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDyFuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yTzGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAYIwggF+MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHSFgMBmx9833s+9KTeqAx2+7c0XMB8GA1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYIKwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA9BgNVHSAENjA0MAsGCWCGSAGG/WwCATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgGBmeBDAECAzANBgkqhkiG9w0BAQsFAAOCAQEAkPFwyyiXaZd8dP3A+iZ7U6utzWX9upwGnIrXWkOH7U1MVl+twcW1BSAuWdH/SvWgKtiwla3JLko716f2b4gp/DA/JIS7w7d7kwcsr4drdjPtAFVSslme5LnQ89/nD/7d+MS5EHKBCQRfz5eeLjJ1js+aWNJXMX43AYGyZm0pGrFmCW3RbpD0ufovARTFXFZkAdl9h6g4U5+LXUZtXMYnhIHUfoyMo5tS58aI7Dd8KvvwVVo4chDYABPPTHPbqjc1qCmBaZx2vN4Ye5DUys/vZwP9BFohFrH/6j/f3IL16/RZkiMNJCqVJUzKoZHm1Lesh3Sz8W2jmdv51b2EQJ8HmA==", + ], + "other" : [ + "MIIOCzCCDPOgAwIBAgIRAMg6iqXyEfImEMVm/IELGKQwDQYJKoZIhvcNAQELBQAwOzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczEMMAoGA1UEAxMDV1IyMB4XDTI0MTEwNDA4Mzc0N1oXDTI1MDEyNzA4Mzc0NlowFzEVMBMGA1UEAwwMKi5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETrFzvW664cnLp0nsu73DWXOluuWCJdoFXaGl3/kltTQ+DuITdA/VC3vXEIe8QNY7MxnOeK2w3dOdfW4QpQqJJqOCC/cwggvzMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRFC6MFCp3z9dQlFrzA3HibW/pgxDAfBgNVHSMEGDAWgBTeGx7teRXUPjckwyG77DQ5bUKyMDBYBggrBgEFBQcBAQRMMEowIQYIKwYBBQUHMAGGFWh0dHA6Ly9vLnBraS5nb29nL3dyMjAlBggrBgEFBQcwAoYZaHR0cDovL2kucGtpLmdvb2cvd3IyLmNydDCCCc0GA1UdEQSCCcQwggnAggwqLmdvb2dsZS5jb22CFiouYXBwZW5naW5lLmdvb2dsZS5jb22CCSouYmRuLmRldoIVKi5vcmlnaW4tdGVzdC5iZG4uZGV2ghIqLmNsb3VkLmdvb2dsZS5jb22CGCouY3Jvd2Rzb3VyY2UuZ29vZ2xlLmNvbYIYKi5kYXRhY29tcHV0ZS5nb29nbGUuY29tggsqLmdvb2dsZS5jYYILKi5nb29nbGUuY2yCDiouZ29vZ2xlLmNvLmlugg4qLmdvb2dsZS5jby5qcIIOKi5nb29nbGUuY28udWuCDyouZ29vZ2xlLmNvbS5hcoIPKi5nb29nbGUuY29tLmF1gg8qLmdvb2dsZS5jb20uYnKCDyouZ29vZ2xlLmNvbS5jb4IPKi5nb29nbGUuY29tLm14gg8qLmdvb2dsZS5jb20udHKCDyouZ29vZ2xlLmNvbS52boILKi5nb29nbGUuZGWCCyouZ29vZ2xlLmVzggsqLmdvb2dsZS5mcoILKi5nb29nbGUuaHWCCyouZ29vZ2xlLml0ggsqLmdvb2dsZS5ubIILKi5nb29nbGUucGyCCyouZ29vZ2xlLnB0gg8qLmdvb2dsZWFwaXMuY26CESouZ29vZ2xldmlkZW8uY29tggwqLmdzdGF0aWMuY26CECouZ3N0YXRpYy1jbi5jb22CD2dvb2dsZWNuYXBwcy5jboIRKi5nb29nbGVjbmFwcHMuY26CEWdvb2dsZWFwcHMtY24uY29tghMqLmdvb2dsZWFwcHMtY24uY29tggxna2VjbmFwcHMuY26CDiouZ2tlY25hcHBzLmNughJnb29nbGVkb3dubG9hZHMuY26CFCouZ29vZ2xlZG93bmxvYWRzLmNughByZWNhcHRjaGEubmV0LmNughIqLnJlY2FwdGNoYS5uZXQuY26CEHJlY2FwdGNoYS1jbi5uZXSCEioucmVjYXB0Y2hhLWNuLm5ldIILd2lkZXZpbmUuY26CDSoud2lkZXZpbmUuY26CEWFtcHByb2plY3Qub3JnLmNughMqLmFtcHByb2plY3Qub3JnLmNughFhbXBwcm9qZWN0Lm5ldC5jboITKi5hbXBwcm9qZWN0Lm5ldC5jboIXZ29vZ2xlLWFuYWx5dGljcy1jbi5jb22CGSouZ29vZ2xlLWFuYWx5dGljcy1jbi5jb22CF2dvb2dsZWFkc2VydmljZXMtY24uY29tghkqLmdvb2dsZWFkc2VydmljZXMtY24uY29tghFnb29nbGV2YWRzLWNuLmNvbYITKi5nb29nbGV2YWRzLWNuLmNvbYIRZ29vZ2xlYXBpcy1jbi5jb22CEyouZ29vZ2xlYXBpcy1jbi5jb22CFWdvb2dsZW9wdGltaXplLWNuLmNvbYIXKi5nb29nbGVvcHRpbWl6ZS1jbi5jb22CEmRvdWJsZWNsaWNrLWNuLm5ldIIUKi5kb3VibGVjbGljay1jbi5uZXSCGCouZmxzLmRvdWJsZWNsaWNrLWNuLm5ldIIWKi5nLmRvdWJsZWNsaWNrLWNuLm5ldIIOZG91YmxlY2xpY2suY26CECouZG91YmxlY2xpY2suY26CFCouZmxzLmRvdWJsZWNsaWNrLmNughIqLmcuZG91YmxlY2xpY2suY26CEWRhcnRzZWFyY2gtY24ubmV0ghMqLmRhcnRzZWFyY2gtY24ubmV0gh1nb29nbGV0cmF2ZWxhZHNlcnZpY2VzLWNuLmNvbYIfKi5nb29nbGV0cmF2ZWxhZHNlcnZpY2VzLWNuLmNvbYIYZ29vZ2xldGFnc2VydmljZXMtY24uY29tghoqLmdvb2dsZXRhZ3NlcnZpY2VzLWNuLmNvbYIXZ29vZ2xldGFnbWFuYWdlci1jbi5jb22CGSouZ29vZ2xldGFnbWFuYWdlci1jbi5jb22CGGdvb2dsZXN5bmRpY2F0aW9uLWNuLmNvbYIaKi5nb29nbGVzeW5kaWNhdGlvbi1jbi5jb22CJCouc2FmZWZyYW1lLmdvb2dsZXN5bmRpY2F0aW9uLWNuLmNvbYIWYXBwLW1lYXN1cmVtZW50LWNuLmNvbYIYKi5hcHAtbWVhc3VyZW1lbnQtY24uY29tggtndnQxLWNuLmNvbYINKi5ndnQxLWNuLmNvbYILZ3Z0Mi1jbi5jb22CDSouZ3Z0Mi1jbi5jb22CCzJtZG4tY24ubmV0gg0qLjJtZG4tY24ubmV0ghRnb29nbGVmbGlnaHRzLWNuLm5ldIIWKi5nb29nbGVmbGlnaHRzLWNuLm5ldIIMYWRtb2ItY24uY29tgg4qLmFkbW9iLWNuLmNvbYIUZ29vZ2xlc2FuZGJveC1jbi5jb22CFiouZ29vZ2xlc2FuZGJveC1jbi5jb22CHiouc2FmZW51cC5nb29nbGVzYW5kYm94LWNuLmNvbYINKi5nc3RhdGljLmNvbYIUKi5tZXRyaWMuZ3N0YXRpYy5jb22CCiouZ3Z0MS5jb22CESouZ2NwY2RuLmd2dDEuY29tggoqLmd2dDIuY29tgg4qLmdjcC5ndnQyLmNvbYIQKi51cmwuZ29vZ2xlLmNvbYIWKi55b3V0dWJlLW5vY29va2llLmNvbYILKi55dGltZy5jb22CC2FuZHJvaWQuY29tgg0qLmFuZHJvaWQuY29tghMqLmZsYXNoLmFuZHJvaWQuY29tggRnLmNuggYqLmcuY26CBGcuY2+CBiouZy5jb4IGZ29vLmdsggp3d3cuZ29vLmdsghRnb29nbGUtYW5hbHl0aWNzLmNvbYIWKi5nb29nbGUtYW5hbHl0aWNzLmNvbYIKZ29vZ2xlLmNvbYISZ29vZ2xlY29tbWVyY2UuY29tghQqLmdvb2dsZWNvbW1lcmNlLmNvbYIIZ2dwaHQuY26CCiouZ2dwaHQuY26CCnVyY2hpbi5jb22CDCoudXJjaGluLmNvbYIIeW91dHUuYmWCC3lvdXR1YmUuY29tgg0qLnlvdXR1YmUuY29tghFtdXNpYy55b3V0dWJlLmNvbYITKi5tdXNpYy55b3V0dWJlLmNvbYIUeW91dHViZWVkdWNhdGlvbi5jb22CFioueW91dHViZWVkdWNhdGlvbi5jb22CD3lvdXR1YmVraWRzLmNvbYIRKi55b3V0dWJla2lkcy5jb22CBXl0LmJlggcqLnl0LmJlghphbmRyb2lkLmNsaWVudHMuZ29vZ2xlLmNvbYITKi5hbmRyb2lkLmdvb2dsZS5jboISKi5jaHJvbWUuZ29vZ2xlLmNughYqLmRldmVsb3BlcnMuZ29vZ2xlLmNuMBMGA1UdIAQMMAowCAYGZ4EMAQIBMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jLnBraS5nb29nL3dyMi85VVZiTjB3NUU2WS5jcmwwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdgDPEVbu1S58r/OHW9lpLpvpGnFnSrAX7KwB0lt3zsw7CAAAAZL2ibUlAAAEAwBHMEUCIQClduzISm51SeFjpmbBW2U2zwk7fTiaWsvddRbHLZH06gIgT3wbf1ZoTtBVj/Jl3IdjFEl2bcEL4w5kmVPG/akmNeEAdgDm0jFjQHeMwRBBBtdxuc7B0kD2loSG+7qHMh39HjeOUAAAAZL2ibULAAAEAwBHMEUCIQC40RSwIuAXPdf9rShZXGrA6elTY/2cmNmznJ5N1ZWq8QIgG4cgjpSbYYIzZQ3oggZxnyHc8hv55AeQv6F2xpZVt2UwDQYJKoZIhvcNAQELBQADggEBABflcY9wxTtmQuEouJqRhZ1D9Ds2Pl2cB3i0aA/Vbrcc/rcgW+RPUmN5d8Kh4WdeqCIMYpNNambI4n9XPnATN78CE/Q568b5msgTZrU5Fo8OzuDUdi9cjo3BH7S6un6o8odYXvUzdHuHNDUvQb8jhdbNOYbXW6fWfWBrlq1qwploqWeJghFvLOJU9+GQlgHv9+mO91tz/5DQfYgsgg7z9mQ+3KHHkgUrN5lW0y9huzGx96/exehf89FmpwPlRn82OWvX3qWhvq58acyqjGFpsGzGhAtWqBq8Ms184g6ndrBJVTsFye7ov5a3RHuhN/wbLhPJf1CzHRBhuGH2//Hko5g=", + "MIIFCzCCAvOgAwIBAgIQf/AFoHxM3tEArZ1mpRB7mDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjMxMjEzMDkwMDAwWhcNMjkwMjIwMTQwMDAwWjA7MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQwwCgYDVQQDEwNXUjIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCp/5x/RR5wqFOfytnlDd5GV1d9vI+aWqxG8YSau5HbyfsvAfuSCQAWXqAc+MGr+XgvSszYhaLYWTwO0xj7sfUkDSbutltkdnwUxy96zqhMt/TZCPzfhyM1IKjiaeKMTj+xWfpgoh6zySBTGYLKNlNtYE3pAJH8do1cCA8Kwtzxc2vFE24KT3rC8gIcLrRjg9ox9i11MLL7q8Ju26nADrn5Z9TDJVd06wW06Y613ijNzHoU5HEDy01hLmFXxRmpC5iEGuh5KdmyjS//V2pm4M6rlagplmNwEmceOuHbsCFx13ye/aoXbv4r+zgXFNFmp6+atXDMyGOBOozAKql2N87jAgMBAAGjgf4wgfswDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTeGx7teRXUPjckwyG77DQ5bUKyMDAfBgNVHSMEGDAWgBTkrysmcRorSCeFL1JmLO/wiRNxPjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAKGGGh0dHA6Ly9pLnBraS5nb29nL3IxLmNydDArBgNVHR8EJDAiMCCgHqAchhpodHRwOi8vYy5wa2kuZ29vZy9yL3IxLmNybDATBgNVHSAEDDAKMAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAgEARXWL5R87RBOWGqtY8TXJbz3S0DNKhjO6V1FP7sQ02hYSTL8Tnw3UVOlIecAwPJQl8hr0ujKUtjNyC4XuCRElNJThb0Lbgpt7fyqaqf9/qdLeSiDLs/sDA7j4BwXaWZIvGEaYzq9yviQmsR4ATb0IrZNBRAq7x9UBhb+TV+PfdBJTDhEl05vc3ssnbrPCuTNiOcLgNeFbpwkuGcuRKnZc8d/KI4RApW//mkHgte8y0YWuryUJ8GLFbsLIbjL9uNrizkqRSvOFVU6xddZIMy9vhNkSXJ/UcZhjJY1pXAprffJBvei7j+Qi151lRehMCofa6WBmiA4fx+FOVsV2/7R6V2nyAiIJJkEd2nSi5SnzxJrlXdaqev3htytmOPvoKWa676ATL/hzfvDaQBEcXd2Ppvy+275W+DKcH0FBbX62xevGiza3F4ydzxl6NJ8hk8R+dDXSqv1MbRT1ybB5W0k8878XSOjvmiYTDIfyc9acxVJrY/cykHipa+te1pOhv7wYPYtZ9orGBV5SGOJm4NrB3K1aJar0RfzxC3ikr7Dyc6QwqDTBU39CluVIQeuQRgwG3MuSxl7zRERDRilGoKb8uY45JzmxWuKxrfwT/478JuHU/oTxUFqOl2stKnn7QGTq8z29W+GgBLCXSBxC9epaHM0myFH/FJlniXJfHeytWt0=", + "MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBXMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UECxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIwMDYxOTAwMDA0MloXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoTGUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFIxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAthECix7joXebO9y/lD63ladAPKH9gvl9MgaCcfb2jH/76Nu8ai6Xl6OMS/kr9rH5zoQdsfnFl97vufKj6bwSiV6nqlKr+CMny6SxnGPb15l+8Ape62im9MZaRw1NEDPjTrETo8gYbEvs/AmQ351kKSUjB6G00j0uYODP0gmHu81I8E3CwnqIiru6z1kZ1q+PsAewnjHxgsHA3y6mbWwZDrXYfiYaRQM9sHmklCitD38m5agI/pboPGiUU+6DOogrFZYJsuB6jC511pzrp1Zkj5ZPaK49l8KEj8C8QMALXL32h7M1bKwYUH+E4EzNktMg6TO8UpmvMrUpsyUqtEj5cuHKZPfmghCN6J3Cioj6OGaK/GP5Afl4/Xtcd/p2h/rs37EOeZVXtL0m79YB0esWCruOC7XFxYpVq9Os6pFLKcwZpDIlTirxZUTQAs6qzkm06p98g7BAe+dDq6dso499iYH6TKX/1Y7DzkvgtdizjkXPdsDtQCv9Uw+wp9U7DbGKogPeMa3Md+pvez7W35EiEua++tgy/BBjFFFy3l3WFpO9KWgz7zpm7AeKJt8T11dleCfeXkkUAKIAf5qoIbapsZWwpbkNFhHax2xIPEDgfg1azVY80ZcFuctL7TlLnMQ/0lUTbiSw1nH69MG6zO0b9f6BQdgAmD06yK56mDcYBZUCAwEAAaOCATgwggE0MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTkrysmcRorSCeFL1JmLO/wiRNxPjAfBgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzBgBggrBgEFBQcBAQRUMFIwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnBraS5nb29nL2dzcjEwKQYIKwYBBQUHMAKGHWh0dHA6Ly9wa2kuZ29vZy9nc3IxL2dzcjEuY3J0MDIGA1UdHwQrMCkwJ6AloCOGIWh0dHA6Ly9jcmwucGtpLmdvb2cvZ3NyMS9nc3IxLmNybDA7BgNVHSAENDAyMAgGBmeBDAECATAIBgZngQwBAgIwDQYLKwYBBAHWeQIFAwIwDQYLKwYBBAHWeQIFAwMwDQYJKoZIhvcNAQELBQADggEBADSkHrEoo9C0dhemMXoh6dFSPsjbdBZBiLg9NR3t5P+T4Vxfq7vqfM/b5A3Ri1fyJm9bvhdGaJQ3b2t6yMAYN/olUazsaL+yyEn9WprKASOshIArAoyZl+tJaox118fessmXn1hIVw41oeQa1v1vg4Fv74zPl6/AhSrw9U5pCZEt4Wi4wStz6dTZ/CLANx8LZh1J7QJVj2fhMtfTJr9w4z30Z209fOU0iOMy+qduBmpvvYuR7hZL6Dupszfnw0Skfths18dG9ZKb59UhvmaSGZRVbNQpsg3BZlvid0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8=" + ], + "invalid" : [ + "MIIHXDCCBkSgAwIBAgIIU0E9kXjSjCcwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzIwHhcNMTcwNDI3MDgzMDAwWhcNMTcwNzIwMDgzMDAwWjBmMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEVMBMGA1UEAwwMKi5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvU0whUatntNonctaUdJSys2T0fw+Idy/GS0xd0dWIviAdKTHrFuz3+9MTD8ZRTU1bsWl3BnS7ZMeEoOexFZeb6OCBPQwggTwMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwggOzBgNVHREEggOqMIIDpoIMKi5nb29nbGUuY29tgg0qLmFuZHJvaWQuY29tghYqLmFwcGVuZ2luZS5nb29nbGUuY29tghIqLmNsb3VkLmdvb2dsZS5jb22CDiouZ2NwLmd2dDIuY29tghYqLmdvb2dsZS1hbmFseXRpY3MuY29tggsqLmdvb2dsZS5jYYILKi5nb29nbGUuY2yCDiouZ29vZ2xlLmNvLmlugg4qLmdvb2dsZS5jby5qcIIOKi5nb29nbGUuY28udWuCDyouZ29vZ2xlLmNvbS5hcoIPKi5nb29nbGUuY29tLmF1gg8qLmdvb2dsZS5jb20uYnKCDyouZ29vZ2xlLmNvbS5jb4IPKi5nb29nbGUuY29tLm14gg8qLmdvb2dsZS5jb20udHKCDyouZ29vZ2xlLmNvbS52boILKi5nb29nbGUuZGWCCyouZ29vZ2xlLmVzggsqLmdvb2dsZS5mcoILKi5nb29nbGUuaHWCCyouZ29vZ2xlLml0ggsqLmdvb2dsZS5ubIILKi5nb29nbGUucGyCCyouZ29vZ2xlLnB0ghIqLmdvb2dsZWFkYXBpcy5jb22CDyouZ29vZ2xlYXBpcy5jboIUKi5nb29nbGVjb21tZXJjZS5jb22CESouZ29vZ2xldmlkZW8uY29tggwqLmdzdGF0aWMuY26CDSouZ3N0YXRpYy5jb22CCiouZ3Z0MS5jb22CCiouZ3Z0Mi5jb22CFCoubWV0cmljLmdzdGF0aWMuY29tggwqLnVyY2hpbi5jb22CECoudXJsLmdvb2dsZS5jb22CFioueW91dHViZS1ub2Nvb2tpZS5jb22CDSoueW91dHViZS5jb22CFioueW91dHViZWVkdWNhdGlvbi5jb22CCyoueXRpbWcuY29tghphbmRyb2lkLmNsaWVudHMuZ29vZ2xlLmNvbYILYW5kcm9pZC5jb22CG2RldmVsb3Blci5hbmRyb2lkLmdvb2dsZS5jboIcZGV2ZWxvcGVycy5hbmRyb2lkLmdvb2dsZS5jboIEZy5jb4IGZ29vLmdsghRnb29nbGUtYW5hbHl0aWNzLmNvbYIKZ29vZ2xlLmNvbYISZ29vZ2xlY29tbWVyY2UuY29tghhzb3VyY2UuYW5kcm9pZC5nb29nbGUuY26CCnVyY2hpbi5jb22CCnd3dy5nb28uZ2yCCHlvdXR1LmJlggt5b3V0dWJlLmNvbYIUeW91dHViZWVkdWNhdGlvbi5jb20waAYIKwYBBQUHAQEEXDBaMCsGCCsGAQUFBzAChh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lBRzIuY3J0MCsGCCsGAQUFBzABhh9odHRwOi8vY2xpZW50czEuZ29vZ2xlLmNvbS9vY3NwMB0GA1UdDgQWBBT9WhKAC80EX/JNWSgDcCNXGUD4NzAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFErdBhYbvPZotXb1gba7Yhq6WoEvMCEGA1UdIAQaMBgwDAYKKwYBBAHWeQIFATAIBgZngQwBAgIwMAYDVR0fBCkwJzAloCOgIYYfaHR0cDovL3BraS5nb29nbGUuY29tL0dJQUcyLmNybDANBgkqhkiG9w0BAQsFAAOCAQEAIcipac4Cm3i9wDM6dVE6d1So6HBDSrJsjO+MOvF2BVeXSz7kZE/9qNAnb/gPiNDwak11ebI6WPWzovsKR8rFRpCFosWAOW+0owY/mDDhhc+cjpObpOqWX8LMJK4LUQhrf7WE2OboHZgBLpiiuWnliPWFP4Zc8tkHGkCE+H67cjjA8EbVjFoTFyGb3E9d+fy4vjwte/a2zOgtb9a798PXPFh4DPEANr24yIo4Og/TPu/cGOCOn8dmfgnH1Dyje8Pr79IIGKw/d9jpAQhlFZYKSyTzLfHzv/7dhQuIUkAy3t0wuDC3m+TMILHRi1YPvnbpW5J8hNoySW6YP8QIQhOrPg==" + ] +} diff --git a/WireAPI/Tests/WireAPITests/Backend/Resources/public-keys.json b/WireAPI/Tests/WireAPITests/Backend/Resources/public-keys.json new file mode 100644 index 00000000000..bc3edd08600 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/Backend/Resources/public-keys.json @@ -0,0 +1,4 @@ +{ + "wire" : "MIIE/jCCA+agAwIBAgIQBmeNK7xaqmvwoGsKbGiEuzANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5EaWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTcxMjEyMDAwMDAwWhcNMTkwMjAxMTIwMDAwWjBKMQswCQYDVQQGEwJDSDEMMAoGA1UEBxMDWnVnMRgwFgYDVQQKEw9XaXJlIFN3aXNzIEdtYkgxEzARBgNVBAMMCioud2lyZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt5jMFa6+dUph+A01fd1WNSeohW2XhepCcJxjqb+xYzXlNMrRuj0UqczE0A+0PMHpWJG+lmwoR59fymLXklyzi5mK5nzUhJXVurG2myMnnpiN6Z730NxrlyTfmlOFi4rqNny8bqkmJj2ZFj2cZp2J3ipYvu7AB6gifHaY4zsd6kIKHY05d34SNDiwGx+Bv6RatxVCYHO8sc9QOjKSb+b4G8vZ4nWeM82Iz8ah5duYhbVYzeJ+5xgmgP2D5Xk18d8A2tW7bDhhwsNp3QLzk1vxTWyAU2SuA6rOF3/XEeiTW47KOh4tMgcdiSvK9sESZ2Xq/5/YnUQzT4WP2+x4jZNitAgMBAAGjggHbMIIB1zAfBgNVHSMEGDAWgBQPgGEcgjFh1S8o541GOLQs4cbZ4jAdBgNVHQ4EFgQU/0iA8JzB4tDtwNB/3NyYfCtfTZwwHwYDVR0RBBgwFoIKKi53aXJlLmNvbYIId2lyZS5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBrBgNVHR8EZDBiMC+gLaArhilodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc3NjYS1zaGEyLWc2LmNybDAvoC2gK4YpaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nNi5jcmwwTAYDVR0gBEUwQzA3BglghkgBhv1sAQEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAIBgZngQwBAgIwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAc6v6cf/EQmmeGU2nC87F6QgEIAIL3svgabImao3f01QFVxC0XX2Cf9+wofijspqq5Uj80nb04o5HNnZWX1agJmqp8jTYH2hw4+uiwFCld0QEptHMrCwEAyyouf0/cl2dfRv2V8m29W6Qb4+7pc1rEbFLl3fywmjgzpGkr1+cKE7pwkpgKqhulKkE4CDXant0Slj7cvDisSPy/kInJ5uHI29Z/SBCpACyHah6lkdIQyTo4uem1XH6i5UP9sTvCAZl0acHcPsvcJ50LeJvJC7sPNXr60xZYLIK5LIVrSSRhxtOB1WPMbzIQc5bF2LcSjXJNvXA5+RCO79om91mlheqPQ==", + "some" : "MIIE/jCCA+agAwIBAgIQBmeNK7xaqmvwoGsKbGiEuzANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5EaWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTcxMjEyMDAwMDAwWhcNMTkwMjAxMTIwMDAwWjBKMQswCQYDVQQGEwJDSDEMMAoGA1UEBxMDWnVnMRgwFgYDVQQKEw9XaXJlIFN3aXNzIEdtYkgxEzARBgNVBAMMCioud2lyZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt5jMFa6+dUph+A01fd1WNSeohW2XhepCcJxjqb+xYzXlNMrRuj0UqczE0A+0PMHpWJG+lmwoR59fymLXklyzi5mK5nzUhJXVurG2myMnnpiN6Z730NxrlyTfmlOFi4rqNny8bqkmJj2ZFj2cZp2J3ipYvu7AB6gifHaY4zsd6kIKHY05d34SNDiwGx+Bv6RatxVCYHO8sc9QOjKSb+b4G8vZ4nWeM82Iz8ah5duYhbVYzeJ+5xgmgP2D5Xk18d8A2tW7bDhhwsNp3QLzk1vxTWyAU2SuA6rOF3/XEeiTW47KOh4tMgcdiSvK9sESZ2Xq/5/YnUQzT4WP2+x4jZNitAgMBAAGjggHbMIIB1zAfBgNVHSMEGDAWgBQPgGEcgjFh1S8o541GOLQs4cbZ4jAdBgNVHQ4EFgQU/0iA8JzB4tDtwNB/3NyYfCtfTZwwHwYDVR0RBBgwFoIKKi53aXJlLmNvbYIId2lyZS5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBrBgNVHR8EZDBiMC+gLaArhilodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc3NjYS1zaGEyLWc2LmNybDAvoC2gK4YpaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nNi5jcmwwTAYDVR0gBEUwQzA3BglghkgBhv1sAQEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAIBgZngQwBAgIwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAc6v6cf/EQmmeGU2nC87F6QgEIAIL3svgabImao3f01QFVxC0XX2Cf9+wofijspqq5Uj80nb04o5HNnZWX1agJmqp8jTYH2hw4+uiwFCld0QEptHMrCwEAyyouf0/cl2dfRv2V8m29W6Qb4+7pc1rEbFLl3fywmjgzpGkr1+cKE7pwkpgKqhulKkE4CDXant0Slj7cvDisSPy/kInJ5uHI29Z/SBCpACyHah6lkdIQyTo4uem1XH6i5UP9sTvCAZl0acHcPsvcJ50LeJvJC7sPNXr60xZYLIK5LIVrSSRhxtOB1WPMbzIQc5bF2LcSjXJNvXA5+RCO79om91mlheqPQ==" +} diff --git a/WireAPI/Tests/WireAPITests/Backend/ServerTrustValidatorTests.swift b/WireAPI/Tests/WireAPITests/Backend/ServerTrustValidatorTests.swift new file mode 100644 index 00000000000..1606d1e9370 --- /dev/null +++ b/WireAPI/Tests/WireAPITests/Backend/ServerTrustValidatorTests.swift @@ -0,0 +1,66 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireTestingPackage +import XCTest + +@testable import WireAPI + +final class ServerTrustValidatorTests: XCTestCase { + + func testValidate_whenNoMatchingHosts() async throws { + let sut = ServerTrustValidator( + pinnedKeys: [ + try PinnedKey(key: PublicKeys.wire, hosts: [.equals("prod-nginz-https.wire.com")]) + ] + ) + + try await sut.validate(trust: .other, host: "example.com") + } + + func testValidate_whenInvalidServerTrust() async throws { + // GIVEN + let sut = ServerTrustValidator( + pinnedKeys: [ + try PinnedKey(key: PublicKeys.wire, hosts: [.equals("prod-nginz-https.wire.com")]) + ] + ) + + // WHEN, THEN + await XCTAssertThrowsErrorAsync( + ServerTrustValidator.Failure.evaluatingServerTrustFailed, + when: { try await sut.validate(trust: .invalid, host: "prod-nginz-https.wire.com") } + ) + } + + func testValidate_whenNoMatchingPublicKey() async throws { + // GIVEN + let sut = ServerTrustValidator( + pinnedKeys: [ + try PinnedKey(key: PublicKeys.wire, hosts: [.equals("example.com")]) + ] + ) + + // WHEN, THEN + await XCTAssertThrowsErrorAsync( + ServerTrustValidator.Failure.noMatchingPublicKey, + when: { try await sut.validate(trust: .other, host: "example.com") } + ) + } + +} diff --git a/WireAPI/Tests/WireAPITests/Helpers/APIServiceSnapshotHelper.swift b/WireAPI/Tests/WireAPITests/Helpers/APIServiceSnapshotHelper.swift index 6a1805f75b6..80a633431c4 100644 --- a/WireAPI/Tests/WireAPITests/Helpers/APIServiceSnapshotHelper.swift +++ b/WireAPI/Tests/WireAPITests/Helpers/APIServiceSnapshotHelper.swift @@ -51,7 +51,7 @@ struct APIServiceSnapshotHelper { /// - line: The line invoking the test. func verifyRequestForAllAPIVersions( - apiService: (() throws -> MockAPIServiceProtocol)? = nil, + apiService: @autoclosure () throws -> MockAPIServiceProtocol = .withResponses([]), when block: (API) async throws -> Void, file: StaticString = #filePath, function: String = #function, @@ -59,7 +59,7 @@ struct APIServiceSnapshotHelper { ) async throws { try await verifyRequest( for: APIVersion.allCases, - apiService: apiService, + apiService: apiService(), when: block, file: file, function: function, @@ -83,7 +83,7 @@ struct APIServiceSnapshotHelper { func verifyRequest( for apiVersions: any Sequence, - apiService: (() throws -> MockAPIServiceProtocol)? = nil, + apiService: @autoclosure () throws -> MockAPIServiceProtocol = .withResponses([]), when block: (API) async throws -> Void, file: StaticString = #filePath, function: String = #function, @@ -92,7 +92,7 @@ struct APIServiceSnapshotHelper { for apiVersion in apiVersions { try await verifyRequest( apiVersion: apiVersion, - apiService: apiService?() ?? .withResponses([]), + apiService: apiService(), when: block, file: file, function: function, diff --git a/WireAPI/Tests/WireAPITests/Helpers/HTTPRequestSnapshotHelper.swift b/WireAPI/Tests/WireAPITests/Helpers/HTTPRequestSnapshotHelper.swift index 2709b6e3c4e..55fb1455f17 100644 --- a/WireAPI/Tests/WireAPITests/Helpers/HTTPRequestSnapshotHelper.swift +++ b/WireAPI/Tests/WireAPITests/Helpers/HTTPRequestSnapshotHelper.swift @@ -40,17 +40,21 @@ struct HTTPRequestSnapshotHelper { function: String = #function, line: UInt = #line ) { - let errorMessage = verifySnapshot( - of: request, - as: .dump, - named: resourceName, - file: file, - testName: function, - line: line - ) + let recordEnabled: SnapshotTestingConfiguration.Record? = ProcessInfo.processInfo + .environment["CI"] == "true" ? .never : nil + withSnapshotTesting(record: recordEnabled) { + let errorMessage = verifySnapshot( + of: request, + as: .dump, + named: resourceName, + file: file, + testName: function, + line: line + ) - if let errorMessage { - XCTFail(errorMessage, file: file, line: line) + if let errorMessage { + XCTFail(errorMessage, file: file, line: line) + } } } @@ -72,18 +76,22 @@ struct HTTPRequestSnapshotHelper { function: String = #function, line: UInt = #line ) { - let errorMessage = verifySnapshot( - of: request, - as: .curl, - named: resourceName, - record: record, - file: file, - testName: function, - line: line - ) + let recordEnabled: SnapshotTestingConfiguration.Record? = ProcessInfo.processInfo + .environment["CI"] == "true" ? .never : nil + withSnapshotTesting(record: recordEnabled) { + let errorMessage = verifySnapshot( + of: request, + as: .curl, + named: resourceName, + record: record, + file: file, + testName: function, + line: line + ) - if let errorMessage { - XCTFail(errorMessage, file: file, line: line) + if let errorMessage { + XCTFail(errorMessage, file: file, line: line) + } } } diff --git a/WireAPI/Tests/WireAPITests/Helpers/TestDataLoading.swift b/WireAPI/Tests/WireAPITests/Helpers/TestDataLoading.swift new file mode 100644 index 00000000000..4f7d58e56db --- /dev/null +++ b/WireAPI/Tests/WireAPITests/Helpers/TestDataLoading.swift @@ -0,0 +1,36 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +enum JSONLoadFailure: Error { + case resourceNotFound(fileName: String) +} + +extension Decodable { + + /// Instantiates `self` from a JSON file with the given `fileName`. + static func loadJSON(fileName: String) throws -> Self { + guard let url = Bundle.module.url(forResource: fileName, withExtension: "json") else { + throw JSONLoadFailure.resourceNotFound(fileName: fileName) + } + let data = try Data(contentsOf: url) + return try JSONDecoder().decode(Self.self, from: data) + } + +} diff --git a/WireAPI/Tests/WireAPITests/Mocks/MockURLAuthenticationChallengeSender.swift b/WireAPI/Tests/WireAPITests/Mocks/MockURLAuthenticationChallengeSender.swift new file mode 100644 index 00000000000..4035091d16c --- /dev/null +++ b/WireAPI/Tests/WireAPITests/Mocks/MockURLAuthenticationChallengeSender.swift @@ -0,0 +1,30 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +@objc +final class MockURLAuthenticationChallengeSender: NSObject, URLAuthenticationChallengeSender { + + func use(_ credential: URLCredential, for challenge: URLAuthenticationChallenge) {} + + func continueWithoutCredential(for challenge: URLAuthenticationChallenge) {} + + func cancel(_ challenge: URLAuthenticationChallenge) {} + +} diff --git a/WireAPI/Sources/WireAPI/Network/PushChannel/PushChannelServiceError.swift b/WireAPI/Tests/WireAPITests/Mocks/MockURLProtectionSpace.swift similarity index 79% rename from WireAPI/Sources/WireAPI/Network/PushChannel/PushChannelServiceError.swift rename to WireAPI/Tests/WireAPITests/Mocks/MockURLProtectionSpace.swift index 2083c9bcfa0..3838899ba8b 100644 --- a/WireAPI/Sources/WireAPI/Network/PushChannel/PushChannelServiceError.swift +++ b/WireAPI/Tests/WireAPITests/Mocks/MockURLProtectionSpace.swift @@ -18,12 +18,11 @@ import Foundation -/// Errors originating from `PushChannelService`. +final class MockURLProtectionSpace: URLProtectionSpace, @unchecked Sendable { -public enum PushChannelServiceError: Error { - - /// An access token is required but none is available. - - case missingAccessToken + var mockServerTrust: SecTrust? + override var serverTrust: SecTrust? { + mockServerTrust + } } diff --git a/wire-ios/Wire-iOS/Sources/Analytics/CountlyInstance.swift b/WireAPI/Tests/WireAPITests/Mocks/PublicKeys.swift similarity index 72% rename from wire-ios/Wire-iOS/Sources/Analytics/CountlyInstance.swift rename to WireAPI/Tests/WireAPITests/Mocks/PublicKeys.swift index d6c60d4964e..0be87b6a104 100644 --- a/wire-ios/Wire-iOS/Sources/Analytics/CountlyInstance.swift +++ b/WireAPI/Tests/WireAPITests/Mocks/PublicKeys.swift @@ -17,13 +17,16 @@ // import Foundation -import WireAnalytics -protocol CountlyInstance { - func recordEvent(_ key: String, segmentation: [String: String]?) - func start(with config: WireCountlyConfig) +struct PublicKeys: Decodable { + let wire: Data + let some: Data - static func sharedInstance() -> Self -} + static var wire: Data { + get throws { try loadJSON(fileName: "public-keys").wire } + } -extension WireCountly: CountlyInstance {} + static var some: Data { + get throws { try loadJSON(fileName: "public-keys").some } + } +} diff --git a/WireAPI/Tests/WireAPITests/Mocks/SecTrust+Mock.swift b/WireAPI/Tests/WireAPITests/Mocks/SecTrust+Mock.swift new file mode 100644 index 00000000000..12748cfb7ba --- /dev/null +++ b/WireAPI/Tests/WireAPITests/Mocks/SecTrust+Mock.swift @@ -0,0 +1,69 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +extension SecTrust { + + enum Failure: Error { + case failedToCreateCertificate + case failedToCreateTrust + } + + static var wire: SecTrust { + get throws { try make(data: Certificates.load().wire) } + } + + static var other: SecTrust { + get throws { try make(data: Certificates.load().other) } + } + + static var invalid: SecTrust { + get throws { try make(data: Certificates.load().invalid) } + } + + static func make(data: [Data]) throws -> SecTrust { + let policy = SecPolicyCreateBasicX509() + + let certificates: [SecCertificate] = try data.compactMap { + guard let cert = SecCertificateCreateWithData(nil, $0 as CFData) else { + throw Failure.failedToCreateCertificate + } + return cert + } + var trust: SecTrust? + guard SecTrustCreateWithCertificates(certificates as CFTypeRef, policy, &trust) == 0, let result = trust else { + throw Failure.failedToCreateTrust + } + + return result + } + +} + +private struct Certificates: Decodable { + + let wire: [Data] + let other: [Data] + let invalid: [Data] + + static func load() throws -> Certificates { + try Certificates.loadJSON(fileName: "certificates") + } + +} diff --git a/WireAPI/Tests/WireAPITests/Network/APIService/APIServiceTests.swift b/WireAPI/Tests/WireAPITests/Network/APIService/APIServiceTests.swift index 6784e30da01..82cbac75133 100644 --- a/WireAPI/Tests/WireAPITests/Network/APIService/APIServiceTests.swift +++ b/WireAPI/Tests/WireAPITests/Network/APIService/APIServiceTests.swift @@ -31,7 +31,10 @@ final class APIServiceTests: XCTestCase { override func setUp() async throws { backendURL = try XCTUnwrap(URL(string: "https://www.example.com")) authenticationManager = MockAuthenticationManagerProtocol() - let networkService = NetworkService(baseURL: backendURL) + let networkService = NetworkService( + baseURL: backendURL, + serverTrustValidator: ServerTrustValidator(pinnedKeys: []) + ) networkService.configure(with: .mockURLSession()) sut = APIService( networkService: networkService, diff --git a/WireAPI/Tests/WireAPITests/Network/NetworkService/NetworkServiceTests.swift b/WireAPI/Tests/WireAPITests/Network/NetworkService/NetworkServiceTests.swift index 3856a8fc0d6..442fef63976 100644 --- a/WireAPI/Tests/WireAPITests/Network/NetworkService/NetworkServiceTests.swift +++ b/WireAPI/Tests/WireAPITests/Network/NetworkService/NetworkServiceTests.swift @@ -24,17 +24,25 @@ import XCTest final class NetworkServiceTests: XCTestCase { + var session: URLSession! var sut: NetworkService! var backendURL: URL! override func setUp() async throws { try await super.setUp() + session = .mockURLSession() backendURL = try XCTUnwrap(URL(string: "https://www.example.com")) - sut = NetworkService(baseURL: backendURL) - sut.configure(with: .mockURLSession()) + sut = NetworkService( + baseURL: backendURL, + serverTrustValidator: ServerTrustValidator(pinnedKeys: [ + try PinnedKey(key: PublicKeys.wire, hosts: [.equals("prod-nginz-https.wire.com")]) + ]) + ) + sut.configure(with: session) } override func tearDown() async throws { + session = nil backendURL = nil sut = nil try await super.tearDown() @@ -91,6 +99,66 @@ final class NetworkServiceTests: XCTestCase { XCTAssertEqual(receivedRequest.url?.absoluteString, backendURL.appendingPathComponent("/foo").absoluteString) } + // MARK: - URLAuthenticationChallenge + + func testUserSessionDidReceiveChallenge_whenNotServerTrustAuthenticationMethod() async throws { + let methods = [ + NSURLAuthenticationMethodClientCertificate, + NSURLAuthenticationMethodNegotiate, + NSURLAuthenticationMethodNTLM, + NSURLAuthenticationMethodHTMLForm, + NSURLAuthenticationMethodHTTPDigest, + NSURLAuthenticationMethodHTTPBasic, + NSURLAuthenticationMethodDefault + ] + + for method in methods { + // Given + let challenge = try Scaffolding.makeAuthenticationChallenge( + authenticationMethod: method, + serverTrust: .invalid + ) + let task = session.dataTask(with: Scaffolding.getRequest) + + // When + let result = await sut.urlSession(session, task: task, didReceive: challenge) + + // Then + XCTAssertEqual(result.0, .performDefaultHandling) + XCTAssertEqual(result.1, Scaffolding.makeCredential()) + } + } + + func testUserSessionDidReceiveChallenge_whenServerTrustValid() async throws { + let challenge = try Scaffolding.makeAuthenticationChallenge( + authenticationMethod: NSURLAuthenticationMethodServerTrust, + serverTrust: .wire + ) + let task = session.dataTask(with: Scaffolding.getRequest) + + // When + let result = await sut.urlSession(session, task: task, didReceive: challenge) + + // Then + XCTAssertEqual(result.0, .performDefaultHandling) + XCTAssertEqual(result.1, Scaffolding.makeCredential()) + } + + func testUserSessionDidReceiveChallenge_whenServerTrustInvalid() async throws { + let challenge = try Scaffolding.makeAuthenticationChallenge( + authenticationMethod: NSURLAuthenticationMethodServerTrust, + serverTrust: .invalid + ) + let task = session.dataTask(with: Scaffolding.getRequest) + + // When + let result = await sut.urlSession(session, task: task, didReceive: challenge) + + // Then + XCTAssertEqual(result.0, .cancelAuthenticationChallenge) + XCTAssertNil(result.1) + } + } private enum Scaffolding { @@ -106,4 +174,31 @@ private enum Scaffolding { return request }() + static func makeCredential() -> URLCredential { + URLCredential(user: "user", password: "password", persistence: .none) + } + + static func makeAuthenticationChallenge( + authenticationMethod: String, + serverTrust: SecTrust + ) throws -> URLAuthenticationChallenge { + let protectionSpace = MockURLProtectionSpace( + host: "prod-nginz-https.wire.com", + port: 8080, + protocol: nil, + realm: nil, + authenticationMethod: authenticationMethod + ) + + protectionSpace.mockServerTrust = serverTrust + + return URLAuthenticationChallenge( + protectionSpace: protectionSpace, + proposedCredential: Scaffolding.makeCredential(), + previousFailureCount: 0, + failureResponse: nil, + error: nil, + sender: MockURLAuthenticationChallengeSender() + ) + } } diff --git a/WireAPI/Tests/WireAPITests/Network/PushChannel/PushChannelTests.swift b/WireAPI/Tests/WireAPITests/Network/PushChannel/PushChannelTests.swift index 5fddc329a03..f9315205895 100644 --- a/WireAPI/Tests/WireAPITests/Network/PushChannel/PushChannelTests.swift +++ b/WireAPI/Tests/WireAPITests/Network/PushChannel/PushChannelTests.swift @@ -201,7 +201,7 @@ private enum Scaffolding { static let receiptModeUpdateEvent = ConversationReceiptModeUpdateEvent( conversationID: conversationID, senderID: senderID, - newRecieptMode: 1 + newReceiptMode: 1 ) static let renameEvent = ConversationRenameEvent( diff --git a/WireAPI/Tests/WireAPITests/UpdateEvent/ConversationEventDecodingTests.swift b/WireAPI/Tests/WireAPITests/UpdateEvent/ConversationEventDecodingTests.swift index ac51a5a235e..867a57b9202 100644 --- a/WireAPI/Tests/WireAPITests/UpdateEvent/ConversationEventDecodingTests.swift +++ b/WireAPI/Tests/WireAPITests/UpdateEvent/ConversationEventDecodingTests.swift @@ -486,7 +486,7 @@ final class ConversationEventDecodingTests: XCTestCase { static let receiptModeUpdateEvent = ConversationReceiptModeUpdateEvent( conversationID: conversationID, senderID: senderID, - newRecieptMode: 1 + newReceiptMode: 1 ) static let renameEvent = ConversationRenameEvent( diff --git a/WireAnalytics/.swiftpm/WireAnalytics.xctestplan b/WireAnalytics/.swiftpm/WireAnalytics.xctestplan deleted file mode 100644 index 4c9af146b50..00000000000 --- a/WireAnalytics/.swiftpm/WireAnalytics.xctestplan +++ /dev/null @@ -1,24 +0,0 @@ -{ - "configurations" : [ - { - "id" : "1AD1FA32-0D55-4D7B-9E4B-C64035D0D3B3", - "name" : "Test Scheme Action", - "options" : { - - } - } - ], - "defaultOptions" : { - "codeCoverage" : true - }, - "testTargets" : [ - { - "target" : { - "containerPath" : "container:", - "identifier" : "WireAnalyticsTests", - "name" : "WireAnalyticsTests" - } - } - ], - "version" : 1 -} diff --git a/WireAnalytics/.swiftpm/xcode/xcshareddata/xcschemes/WireDatadog.xcscheme b/WireAnalytics/.swiftpm/xcode/xcshareddata/xcschemes/WireDatadog.xcscheme deleted file mode 100644 index 704e826360f..00000000000 --- a/WireAnalytics/.swiftpm/xcode/xcshareddata/xcschemes/WireDatadog.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WireAnalytics/Package.swift b/WireAnalytics/Package.swift index 27c2bca8bfc..3d0545e1475 100644 --- a/WireAnalytics/Package.swift +++ b/WireAnalytics/Package.swift @@ -1,56 +1,88 @@ -// swift-tools-version: 5.10 +// swift-tools-version: 6.0 import Foundation import PackageDescription // You can enable/disable Datadog for debugging by overriding the boolean. -let datadogEnabled = hasEnvironmentVariable("ENABLE_DATADOG", "true") +let isDatadogEnabled = hasEnvironmentVariable("ENABLE_DATADOG", "true") +let isCountlyEnabled = hasEnvironmentVariable("ENABLE_COUNTLY", "true") let package = Package( name: "WireAnalytics", platforms: [.iOS(.v16), .macOS(.v12)], products: [ .library(name: "WireAnalytics", targets: ["WireAnalytics"]), - .library(name: "WireDatadog", targets: ["WireDatadog"]), - .library(name: "WireAnalyticsSupport", targets: ["WireAnalyticsSupport"]) + .library(name: "WireAnalyticsSupport", targets: ["WireAnalyticsSupport"]), + .library(name: "WireCountly", targets: ["WireCountly"]), + .library(name: "WireDatadog", targets: ["WireDatadog"]) ], dependencies: [ - .package(url: "https://github.com/DataDog/dd-sdk-ios.git", exact: "2.18.0"), .package(url: "https://github.com/Countly/countly-sdk-ios.git", exact: "24.4.2"), + .package(url: "https://github.com/DataDog/dd-sdk-ios.git", exact: "2.18.0"), + .package(path: "../WireLogging"), .package(path: "../WirePlugins") ], targets: [ .target( name: "WireAnalytics", - dependencies: [ - .product(name: "Countly", package: "countly-sdk-ios") - ], - swiftSettings: swiftSettings - ), - .target( - name: "WireDatadog", - dependencies: datadogDependencies(), - path: "Sources/WireDatadog", - sources: datadogFiles(), - swiftSettings: swiftSettings + dependencies: ["WireLogging"] ), .target( name: "WireAnalyticsSupport", dependencies: ["WireAnalytics"], - plugins: [ - .plugin(name: "SourceryPlugin", package: "WirePlugins") - ] + plugins: [.plugin(name: "SourceryPlugin", package: "WirePlugins")] ), .testTarget( name: "WireAnalyticsTests", dependencies: ["WireAnalytics", "WireAnalyticsSupport"] + ), + + .target( + name: "WireCountly", + dependencies: countlyDependencies() + ["WireAnalytics"], + sources: countlyFiles() + ), + + .target( + name: "WireDatadog", + dependencies: datadogDependencies() + ["WireLogging"], + sources: datadogFiles() + ), + + // This target prevents warnings about `countly-sdk-ios` or `dd-sdk-ios` not being used. + .target( + name: "WarningPrevention", + dependencies: [ + .product(name: "Countly", package: "countly-sdk-ios"), + .product(name: "DatadogCore", package: "dd-sdk-ios") + ] ) ] ) +// MARK: - Countly + +func countlyDependencies() -> [Target.Dependency] { + guard isCountlyEnabled else { + return [] + } + return [ + .product(name: "Countly", package: "countly-sdk-ios") + ] +} + +func countlyFiles() -> [String] { + if isCountlyEnabled { + ["CountlyWrapper.swift"] + } else { + ["CountlyDummy.swift"] + } +} + +// MARK: - Datadog + func datadogDependencies() -> [Target.Dependency] { - guard datadogEnabled else { - // note: in this case SPM will warn that the dd-sdk-ios is not used + guard isDatadogEnabled else { return [] } return [ @@ -63,13 +95,15 @@ func datadogDependencies() -> [Target.Dependency] { } func datadogFiles() -> [String] { - if datadogEnabled { - ["WireDatadog.swift", "LogLevel.swift"] + if isDatadogEnabled { + ["WireDatadog.swift"] } else { - ["WireFakeDatadog.swift", "LogLevel.swift"] + ["WireFakeDatadog.swift"] } } +// MARK: - + func hasEnvironmentVariable(_ name: String, _ value: String? = nil) -> Bool { if let value { ProcessInfo.processInfo.environment[name] == value @@ -78,8 +112,10 @@ func hasEnvironmentVariable(_ name: String, _ value: String? = nil) -> Bool { } } -let swiftSettings: [SwiftSetting] = [ - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("GlobalConcurrency"), - .enableExperimentalFeature("StrictConcurrency") -] +for target in package.targets { + target.swiftSettings = (target.swiftSettings ?? []) + [ + .enableUpcomingFeature("InternalImportsByDefault"), + .enableUpcomingFeature("FullTypedThrows"), + .enableUpcomingFeature("ExistentialAny") + ] +} diff --git a/WireAnalytics/Sources/WarningPrevention/WarningPrevention.swift b/WireAnalytics/Sources/WarningPrevention/WarningPrevention.swift new file mode 100644 index 00000000000..b92d3a9c94c --- /dev/null +++ b/WireAnalytics/Sources/WarningPrevention/WarningPrevention.swift @@ -0,0 +1,22 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +// A signle empty file needed for the `WarningPrevention` target. +// The target is not to be referenced, its purpose is only to not trigger +// warnings about unused dependencies when Countly or Datadog are not +// enabled via environment variables. diff --git a/WireAnalytics/Sources/WireAnalytics/Countly/CountlyProtocol.swift b/WireAnalytics/Sources/WireAnalytics/Countly/CountlyProtocol.swift index eb26be7daba..e6d758e1099 100644 --- a/WireAnalytics/Sources/WireAnalytics/Countly/CountlyProtocol.swift +++ b/WireAnalytics/Sources/WireAnalytics/Countly/CountlyProtocol.swift @@ -16,11 +16,11 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation +public import Foundation // sourcery: AutoMockable -/// Abstraction around `Countly`. -protocol CountlyProtocol { +/// Mirrors the API of `Countly`. +public protocol CountlyProtocol { func resetInstance() diff --git a/WireAnalytics/Sources/WireAnalytics/Events/CallQualitySurveyReview.swift b/WireAnalytics/Sources/WireAnalytics/Events/CallQualitySurveyReview.swift index d0992460bb4..c09d7bf7bcd 100644 --- a/WireAnalytics/Sources/WireAnalytics/Events/CallQualitySurveyReview.swift +++ b/WireAnalytics/Sources/WireAnalytics/Events/CallQualitySurveyReview.swift @@ -16,7 +16,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation +public import Foundation /// User review for call quality. diff --git a/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsEventTracker.swift b/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsEventTracker.swift index b7246be0d9b..97e8ded51c6 100644 --- a/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsEventTracker.swift +++ b/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsEventTracker.swift @@ -16,8 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - // sourcery: AutoMockable /// An object that tracks analytic events. public protocol AnalyticsEventTracker: AnyObject { diff --git a/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsService.swift b/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsService.swift index 5d5bbc38676..fa28bea45bc 100644 --- a/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsService.swift +++ b/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsService.swift @@ -16,8 +16,9 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Countly -import Foundation +public import Foundation + +import WireLogging /// A service for tracking analytics data generated by the user. @@ -30,43 +31,40 @@ public final class AnalyticsService: AnalyticsServiceProtocol { private var countly: (any CountlyProtocol)? private var currentUser: AnalyticsUser? private let baseSegmentation: Set - private let logger: (String) -> Void + private let logger: WireLogger // MARK: - Life cycle /// Create a new `AnalyticsService`. /// /// A non-nil config is needed to track analytics data. - /// - /// - Parameters: - /// - config: A config containing the authentication key and server host. - /// - logger: A function that logs a message on behalf of the service. public convenience init( config: Config?, - logger: @escaping (String) -> Void + deviceModel: String, + deviceOS: String, + countlyProvider: @escaping () -> any CountlyProtocol ) { self.init( config: config, baseSegmentation: [ - .deviceModel(UIDevice.current.model), - .deviceOS(UIDevice.current.systemVersion) + .deviceModel(deviceModel), + .deviceOS(deviceOS) ], - logger: logger, - countlyProvider: { Countly.sharedInstance() } + countlyProvider: countlyProvider ) } init( config: Config?, baseSegmentation: Set, - logger: @escaping (String) -> Void, countlyProvider: @escaping () -> any CountlyProtocol ) { self.config = config self.baseSegmentation = baseSegmentation - self.logger = logger self.countlyProvider = countlyProvider + + self.logger = .analytics } // MARK: - Enable / disable @@ -85,23 +83,22 @@ public final class AnalyticsService: AnalyticsServiceProtocol { /// Start sending analytics data. + @MainActor public func enableTracking() async throws { guard let config else { throw AnalyticsServiceError.serviceIsNotConfigured } - logger("enabling tracking") + logger.debug("enabling tracking") let countly = countlyProvider() self.countly = countly // Countly invokes UI apis so we need to run it on the main thread. - await MainActor.run { - countly.start( - appKey: config.secretKey, - host: config.serverHost - ) - } + countly.start( + appKey: config.secretKey, + host: config.serverHost + ) } /// Stop sending analytics data. @@ -111,7 +108,7 @@ public final class AnalyticsService: AnalyticsServiceProtocol { throw AnalyticsServiceError.serviceIsNotConfigured } - logger("disabling tracking") + logger.debug("disabling tracking") countly.endSession() try clearCurrentUser() @@ -134,7 +131,7 @@ public final class AnalyticsService: AnalyticsServiceProtocol { return } - logger("switching user") + logger.debug("switching user") countly.endSession() @@ -161,7 +158,7 @@ public final class AnalyticsService: AnalyticsServiceProtocol { return } - logger("updating current user") + logger.debug("updating current user") try pushUser( user, @@ -203,7 +200,7 @@ public final class AnalyticsService: AnalyticsServiceProtocol { } private func clearCurrentUser() throws { - logger("clearing current user") + logger.debug("clearing current user") try pushUser( nil, @@ -232,7 +229,7 @@ public final class AnalyticsService: AnalyticsServiceProtocol { ($0.key, $0.value) }) - logger("tracking event: \(event)") + logger.debug("tracking event: \(event)") countly.recordEvent( event.name, diff --git a/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsServiceProtocol.swift b/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsServiceProtocol.swift index 8205d41de37..074ef2db0a3 100644 --- a/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsServiceProtocol.swift +++ b/WireAnalytics/Sources/WireAnalytics/Service/AnalyticsServiceProtocol.swift @@ -16,8 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - /// A service for tracking analytics data generated by the user. public protocol AnalyticsServiceProtocol: AnalyticsEventTracker { diff --git a/WireAnalytics/Sources/WireAnalyticsSupport/MockAnalyticsServiceProtocol.swift b/WireAnalytics/Sources/WireAnalyticsSupport/MockAnalyticsServiceProtocol.swift index ec76b3420b1..f5c97fbf980 100644 --- a/WireAnalytics/Sources/WireAnalyticsSupport/MockAnalyticsServiceProtocol.swift +++ b/WireAnalytics/Sources/WireAnalyticsSupport/MockAnalyticsServiceProtocol.swift @@ -15,7 +15,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. // -import WireAnalytics + +public import WireAnalytics // this mock is generated manually because of (any Error)? // TODO: [WPB-11829] update sourcery diff --git a/WireAnalytics/Sources/WireAnalyticsSupport/Sourcery/sourcery.yml b/WireAnalytics/Sources/WireAnalyticsSupport/Sourcery/sourcery.yml index 6dfea6d2606..b53b65b8305 100644 --- a/WireAnalytics/Sources/WireAnalyticsSupport/Sourcery/sourcery.yml +++ b/WireAnalytics/Sources/WireAnalyticsSupport/Sourcery/sourcery.yml @@ -3,6 +3,6 @@ sources: templates: - ${TARGET_DIR}/Sourcery/AutoMockable.stencil output: - ${DERIVED_SOURCES_DIR} + ${DERIVED_SOURCES_DIR} args: - autoMockableTestableImports: ["WireAnalytics"] + autoMockablePublicImports: ["Foundation", "WireAnalytics"] diff --git a/WireAnalytics/Sources/WireCountly/CountlyDummy.swift b/WireAnalytics/Sources/WireCountly/CountlyDummy.swift new file mode 100644 index 00000000000..44746ba92fd --- /dev/null +++ b/WireAnalytics/Sources/WireCountly/CountlyDummy.swift @@ -0,0 +1,35 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +public import Foundation +public import WireAnalytics + +public typealias CountlyWrapper = CountlyDummy + +public struct CountlyDummy: CountlyProtocol { + + public init() {} + + public func resetInstance() {} + public func start(appKey: String, host: URL) {} + public func setUserValue(_ value: String?, forKey key: String) {} + public func changeDeviceID(_ id: String, mergeData: Bool) {} + public func beginSession() {} + public func endSession() {} + public func recordEvent(_ key: String, segmentation: [String: String]?) {} +} diff --git a/WireAnalytics/Sources/WireAnalytics/Countly/Countly+CountlyProtocol.swift b/WireAnalytics/Sources/WireCountly/CountlyWrapper.swift similarity index 57% rename from WireAnalytics/Sources/WireAnalytics/Countly/Countly+CountlyProtocol.swift rename to WireAnalytics/Sources/WireCountly/CountlyWrapper.swift index e3c360b6f64..50ded49417e 100644 --- a/WireAnalytics/Sources/WireAnalytics/Countly/Countly+CountlyProtocol.swift +++ b/WireAnalytics/Sources/WireCountly/CountlyWrapper.swift @@ -16,10 +16,23 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Countly +public import Foundation +public import WireAnalytics +public import Countly -extension Countly: CountlyProtocol { - func start( +public struct CountlyWrapper: CountlyProtocol { + + var countly: () -> Countly + + public init(countly: @escaping @autoclosure () -> Countly = .sharedInstance()) { + self.countly = countly + } + + public func resetInstance() { + countly().resetInstance() + } + + public func start( appKey: String, host: URL ) { @@ -27,29 +40,44 @@ extension Countly: CountlyProtocol { config.appKey = appKey config.manualSessionHandling = true config.host = host.absoluteString - start(with: config) + countly().start(with: config) } - func changeDeviceID( + public func setUserValue( + _ value: String?, + forKey key: String + ) { + if let value { + Countly.user().set(key, value: value) + } else { + Countly.user().unSet(key) + } + } + + public func changeDeviceID( _ id: String, mergeData: Bool ) { if mergeData { - changeDeviceID(withMerge: id) + countly().changeDeviceID(withMerge: id) } else { - changeDeviceIDWithoutMerge(id) + countly().changeDeviceIDWithoutMerge(id) } } - func setUserValue( - _ value: String?, - forKey key: String + public func beginSession() { + countly().beginSession() + } + + public func endSession() { + countly().endSession() + } + + public func recordEvent( + _ key: String, + segmentation: [String: String]? ) { - if let value { - Countly.user().set(key, value: value) - } else { - Countly.user().unSet(key) - } + countly().recordEvent(key, segmentation: segmentation) } } diff --git a/WireAnalytics/Sources/WireCountly/PrivacyInfo.xcprivacy b/WireAnalytics/Sources/WireCountly/PrivacyInfo.xcprivacy new file mode 120000 index 00000000000..11b855aa14b --- /dev/null +++ b/WireAnalytics/Sources/WireCountly/PrivacyInfo.xcprivacy @@ -0,0 +1 @@ +../../.PrivacyInfo.xcprivacy \ No newline at end of file diff --git a/WireAnalytics/Sources/WireDatadog/WireDatadog.swift b/WireAnalytics/Sources/WireDatadog/WireDatadog.swift index b21fad0da73..627fc2a5734 100644 --- a/WireAnalytics/Sources/WireDatadog/WireDatadog.swift +++ b/WireAnalytics/Sources/WireDatadog/WireDatadog.swift @@ -16,6 +16,9 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +public import Foundation +public import WireLogging + import CryptoKit import DatadogCore import DatadogCrashReporting @@ -29,7 +32,7 @@ public final class WireDatadog { private let applicationID: String private let buildVersion: String private let buildNumber: String - private let logLevel: WireDatadog.LogLevel = .debug + private let logLevel: WireLogLevel = .debug public private(set) var userIdentifier: String private(set) var logger: (any DatadogLogs.LoggerProtocol)? @@ -72,7 +75,7 @@ public final class WireDatadog { let loggerConfiguration = Logger.Configuration( name: "iOS Wire App", networkInfoEnabled: true, - remoteLogThreshold: logLevel.datadogLevel + remoteLogThreshold: logLevel.mapToDatadogLogLevel() ) self.logger = Logger.create(with: loggerConfiguration) } @@ -96,7 +99,7 @@ public final class WireDatadog { Datadog.setUserInfo(id: userIdentifier) logger?.log( - level: logLevel.datadogLevel, + level: logLevel.mapToDatadogLogLevel(), message: "Datadog startMonitoring for device: \(userIdentifier)", error: nil, attributes: nil @@ -104,7 +107,7 @@ public final class WireDatadog { } public func log( - level: WireDatadog.LogLevel, + level: WireLogLevel, message: String, error: (any Error)? = nil, attributes: [String: any Encodable] @@ -113,8 +116,10 @@ public final class WireDatadog { finalAttributes["build_number"] = buildNumber finalAttributes["version"] = buildVersion + let level = level.mapToDatadogLogLevel() + logger?.log( - level: level.datadogLevel, // TODO: [WPB-11881] review this when WireLogger is available as package + level: level, message: message, error: error, attributes: finalAttributes @@ -148,8 +153,8 @@ public final class WireDatadog { } } -extension WireDatadog.LogLevel { - var datadogLevel: LogLevel { +extension WireLogLevel { + func mapToDatadogLogLevel() -> LogLevel { switch self { case .debug: .debug diff --git a/WireAnalytics/Sources/WireDatadog/WireFakeDatadog.swift b/WireAnalytics/Sources/WireDatadog/WireFakeDatadog.swift index 61e7a2c57a3..613aebf8035 100644 --- a/WireAnalytics/Sources/WireDatadog/WireFakeDatadog.swift +++ b/WireAnalytics/Sources/WireDatadog/WireFakeDatadog.swift @@ -16,7 +16,8 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import UIKit +public import Foundation +public import WireLogging public final class WireDatadog { @@ -40,7 +41,7 @@ public final class WireDatadog { } public func log( - level: WireDatadog.LogLevel, + level: WireLogLevel, message: String, error: (any Error)? = nil, attributes: [String: any Encodable] diff --git a/WireUI/.swiftpm/WireAccountImageUI.xctestplan b/WireAnalytics/Tests/TestPlans/AllTests.xctestplan similarity index 54% rename from WireUI/.swiftpm/WireAccountImageUI.xctestplan rename to WireAnalytics/Tests/TestPlans/AllTests.xctestplan index 741557b9235..1ce51f179d1 100644 --- a/WireUI/.swiftpm/WireAccountImageUI.xctestplan +++ b/WireAnalytics/Tests/TestPlans/AllTests.xctestplan @@ -1,8 +1,8 @@ { "configurations" : [ { - "id" : "EE19C776-C34B-4ED9-B55F-54761C5D7C65", - "name" : "Test Scheme Action", + "id" : "BE2F1BC8-EAC1-496B-9F9E-FD0251975803", + "name" : "Configuration 1", "options" : { } @@ -16,9 +16,9 @@ "testTargets" : [ { "target" : { - "containerPath" : "container:", - "identifier" : "WireAccountImageUITests", - "name" : "WireAccountImageUITests" + "containerPath" : "container:WireAnalytics", + "identifier" : "WireAnalyticsTests", + "name" : "WireAnalyticsTests" } } ], diff --git a/WireAnalytics/Tests/WireAnalyticsTests/AnalyticsServiceTests.swift b/WireAnalytics/Tests/WireAnalyticsTests/AnalyticsServiceTests.swift index b5d7916f99e..a990e1e9a0e 100644 --- a/WireAnalytics/Tests/WireAnalyticsTests/AnalyticsServiceTests.swift +++ b/WireAnalytics/Tests/WireAnalyticsTests/AnalyticsServiceTests.swift @@ -24,49 +24,48 @@ import XCTest class AnalyticsServiceTests: XCTestCase { private var sut: AnalyticsService! - private var countly: MockCountlyProtocol! + private var countlyMock: MockCountlyProtocol! override func setUpWithError() throws { - try super.setUpWithError() - countly = MockCountlyProtocol() + countlyMock = .init() sut = AnalyticsService( config: Scaffolding.config, baseSegmentation: Scaffolding.baseSegmentation, - logger: { print($0) }, - countlyProvider: { self.countly } + countlyProvider: { self.countlyMock } ) - countly.startAppKeyHost_MockMethod = { _, _ in } - countly.resetInstance_MockMethod = {} - countly.endSession_MockMethod = {} - countly.beginSession_MockMethod = {} - countly.changeDeviceIDMergeData_MockMethod = { _, _ in } - countly.setUserValueForKey_MockMethod = { _, _ in } - countly.recordEventSegmentation_MockMethod = { _, _ in } + countlyMock.startAppKeyHost_MockMethod = { _, _ in } + countlyMock.resetInstance_MockMethod = {} + countlyMock.endSession_MockMethod = {} + countlyMock.beginSession_MockMethod = {} + countlyMock.changeDeviceIDMergeData_MockMethod = { _, _ in } + countlyMock.setUserValueForKey_MockMethod = { _, _ in } + countlyMock.recordEventSegmentation_MockMethod = { _, _ in } } override func tearDown() { - countly = nil + countlyMock = nil sut = nil - super.tearDown() } func resetMockInvocations() { - countly.startAppKeyHost_Invocations = [] - countly.endSession_Invocations = [] - countly.beginSession_Invocations = [] - countly.changeDeviceIDMergeData_Invocations = [] - countly.setUserValueForKey_Invocations = [] - countly.resetInstance_Invocations = [] + countlyMock.startAppKeyHost_Invocations = [] + countlyMock.endSession_Invocations = [] + countlyMock.beginSession_Invocations = [] + countlyMock.changeDeviceIDMergeData_Invocations = [] + countlyMock.setUserValueForKey_Invocations = [] + countlyMock.resetInstance_Invocations = [] } // MARK: - Tests + @MainActor func testEnableTracking_service_is_not_configured() async throws { // Given a service with no config. let sut = AnalyticsService( config: nil, - logger: { print($0) } + baseSegmentation: Scaffolding.baseSegmentation, + countlyProvider: { self.countlyMock } ) do { @@ -78,12 +77,13 @@ class AnalyticsServiceTests: XCTestCase { } } + @MainActor func testEnableTracking_succeeds() async throws { // When tracking is enabled. try await sut.enableTracking() // Then the service was started. - let invocations = countly.startAppKeyHost_Invocations + let invocations = countlyMock.startAppKeyHost_Invocations guard invocations.count == 1 else { XCTFail("expected 1 invocation, got: \(invocations.count)") @@ -94,7 +94,7 @@ class AnalyticsServiceTests: XCTestCase { XCTAssertEqual(invocations[0].host, Scaffolding.config.serverHost) // Then no session has started yet. - XCTAssertEqual(countly.beginSession_Invocations.count, 0) + XCTAssertEqual(countlyMock.beginSession_Invocations.count, 0) } func testDisableTracking_service_is_not_configured() throws { @@ -110,6 +110,7 @@ class AnalyticsServiceTests: XCTestCase { } } + @MainActor func testDisableTracking_succeeds() async throws { // Given tracking is enabled. try await sut.enableTracking() @@ -119,11 +120,11 @@ class AnalyticsServiceTests: XCTestCase { try sut.disableTracking() // Then any session was ended and the service was reset. - XCTAssertEqual(countly.endSession_Invocations.count, 1) - XCTAssertEqual(countly.resetInstance_Invocations.count, 1) + XCTAssertEqual(countlyMock.endSession_Invocations.count, 1) + XCTAssertEqual(countlyMock.resetInstance_Invocations.count, 1) // Then the user was cleared. - let setUserInvocations = countly.setUserValueForKey_Invocations + let setUserInvocations = countlyMock.setUserValueForKey_Invocations guard setUserInvocations.count == 3 else { XCTFail("expected 3 invocation, got: \(setUserInvocations.count)") @@ -150,6 +151,7 @@ class AnalyticsServiceTests: XCTestCase { } } + @MainActor func testSwitchUser_user_is_same() async throws { // Given tracking is enabled. try await sut.enableTracking() @@ -162,12 +164,13 @@ class AnalyticsServiceTests: XCTestCase { try sut.switchUser(Scaffolding.user) // Then the user was not switched again. - XCTAssertEqual(countly.endSession_Invocations.count, 0) - XCTAssertEqual(countly.changeDeviceIDMergeData_Invocations.count, 0) - XCTAssertEqual(countly.setUserValueForKey_Invocations.count, 0) - XCTAssertEqual(countly.beginSession_Invocations.count, 0) + XCTAssertEqual(countlyMock.endSession_Invocations.count, 0) + XCTAssertEqual(countlyMock.changeDeviceIDMergeData_Invocations.count, 0) + XCTAssertEqual(countlyMock.setUserValueForKey_Invocations.count, 0) + XCTAssertEqual(countlyMock.beginSession_Invocations.count, 0) } + @MainActor func testSwitchUser_succeeds() async throws { // Given tracking is enabled. try await sut.enableTracking() @@ -180,10 +183,10 @@ class AnalyticsServiceTests: XCTestCase { try sut.switchUser(Scaffolding.userWithTeam) // Then the existing session was ended. - XCTAssertEqual(countly.endSession_Invocations.count, 1) + XCTAssertEqual(countlyMock.endSession_Invocations.count, 1) // Then the device id was changed. - let deviceChangeInvocations = countly.changeDeviceIDMergeData_Invocations + let deviceChangeInvocations = countlyMock.changeDeviceIDMergeData_Invocations guard deviceChangeInvocations.count == 1 else { XCTFail("expected 1 device change invocation, got \(deviceChangeInvocations.count)") return @@ -193,7 +196,7 @@ class AnalyticsServiceTests: XCTestCase { XCTAssertEqual(deviceChangeInvocations[0].mergeData, false) // Then the user details were set. - let userSetInvocations = countly.setUserValueForKey_Invocations + let userSetInvocations = countlyMock.setUserValueForKey_Invocations guard userSetInvocations.count == 3 else { XCTFail("expected 3 user set invocations, got \(userSetInvocations.count)") return @@ -208,9 +211,10 @@ class AnalyticsServiceTests: XCTestCase { XCTAssertEqual(userSetInvocations[2].value, String(teamInfo.size.logRound())) // Then a new session was started. - XCTAssertEqual(countly.beginSession_Invocations.count, 1) + XCTAssertEqual(countlyMock.beginSession_Invocations.count, 1) } + @MainActor func testUpdateCurrentUser_no_current_user() async throws { // Given tracking is enabled. try await sut.enableTracking() @@ -221,10 +225,11 @@ class AnalyticsServiceTests: XCTestCase { try sut.updateCurrentUser(Scaffolding.user) // Then the user was not updated. - XCTAssertEqual(countly.changeDeviceIDMergeData_Invocations.count, 0) - XCTAssertEqual(countly.setUserValueForKey_Invocations.count, 0) + XCTAssertEqual(countlyMock.changeDeviceIDMergeData_Invocations.count, 0) + XCTAssertEqual(countlyMock.setUserValueForKey_Invocations.count, 0) } + @MainActor func testUpdateCurrentUser_no_change() async throws { // Given tracking is enabled. try await sut.enableTracking() @@ -237,10 +242,11 @@ class AnalyticsServiceTests: XCTestCase { try sut.updateCurrentUser(Scaffolding.user) // Then no user data changed. - XCTAssertEqual(countly.changeDeviceIDMergeData_Invocations.count, 0) - XCTAssertEqual(countly.setUserValueForKey_Invocations.count, 0) + XCTAssertEqual(countlyMock.changeDeviceIDMergeData_Invocations.count, 0) + XCTAssertEqual(countlyMock.setUserValueForKey_Invocations.count, 0) } + @MainActor func testUpdateCurrentUser_with_change() async throws { // Given tracking is enabled. try await sut.enableTracking() @@ -253,7 +259,7 @@ class AnalyticsServiceTests: XCTestCase { try sut.updateCurrentUser(Scaffolding.userWithTeam) // Then the device id was changed with a merge. - let deviceChangeInvocations = countly.changeDeviceIDMergeData_Invocations + let deviceChangeInvocations = countlyMock.changeDeviceIDMergeData_Invocations guard deviceChangeInvocations.count == 1 else { XCTFail("expected 1 device change invocation, got \(deviceChangeInvocations.count)") return @@ -263,7 +269,7 @@ class AnalyticsServiceTests: XCTestCase { XCTAssertEqual(deviceChangeInvocations[0].mergeData, true) // Then the user details were set. - let userSetInvocations = countly.setUserValueForKey_Invocations + let userSetInvocations = countlyMock.setUserValueForKey_Invocations guard userSetInvocations.count == 3 else { XCTFail("expected 3 user set invocations, got \(userSetInvocations.count)") return @@ -285,9 +291,10 @@ class AnalyticsServiceTests: XCTestCase { sut.trackEvent(Scaffolding.event) // Then no event was tracked. - XCTAssertEqual(countly.recordEventSegmentation_Invocations.count, 0) + XCTAssertEqual(countlyMock.recordEventSegmentation_Invocations.count, 0) } + @MainActor func testTrackEvent_no_current_user() async throws { // Given tracking is enabled. try await sut.enableTracking() @@ -298,9 +305,10 @@ class AnalyticsServiceTests: XCTestCase { sut.trackEvent(Scaffolding.event) // Then no event was tracked. - XCTAssertEqual(countly.recordEventSegmentation_Invocations.count, 0) + XCTAssertEqual(countlyMock.recordEventSegmentation_Invocations.count, 0) } + @MainActor func testTrackEvent_succeeds() async throws { // Given tracking is enabled. try await sut.enableTracking() @@ -312,7 +320,7 @@ class AnalyticsServiceTests: XCTestCase { sut.trackEvent(Scaffolding.event) // Then a single event was tracked. - let recordInvocations = countly.recordEventSegmentation_Invocations + let recordInvocations = countlyMock.recordEventSegmentation_Invocations guard recordInvocations.count == 1 else { XCTFail("expected 1 recordInvocation, got \(recordInvocations.count)") return diff --git a/WireDomain/.swiftpm/xcode/xcshareddata/xcschemes/WireDomainPackage.xcscheme b/WireDomain/.swiftpm/xcode/xcshareddata/xcschemes/WireDomainPackage.xcscheme deleted file mode 100644 index 1ee80a9c74b..00000000000 --- a/WireDomain/.swiftpm/xcode/xcshareddata/xcschemes/WireDomainPackage.xcscheme +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WireDomain/.swiftpm/xcode/xcshareddata/xcschemes/WireDomainPackageSupport.xcscheme b/WireDomain/.swiftpm/xcode/xcshareddata/xcschemes/WireDomainPackageSupport.xcscheme deleted file mode 100644 index daa4f774309..00000000000 --- a/WireDomain/.swiftpm/xcode/xcshareddata/xcschemes/WireDomainPackageSupport.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WireDomain/Package.resolved b/WireDomain/Package.resolved new file mode 100644 index 00000000000..3b2eb2263f3 --- /dev/null +++ b/WireDomain/Package.resolved @@ -0,0 +1,42 @@ +{ + "originHash" : "b0b8539ea418da22b07d0a75a40c21caa3f909f7b2c177680c5fdc645fc1d64a", + "pins" : [ + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-docc-plugin", + "state" : { + "revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64", + "version" : "1.4.3" + } + }, + { + "identity" : "swift-docc-symbolkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-docc-symbolkit", + "state" : { + "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "42a086182681cf661f5c47c9b7dc3931de18c6d7", + "version" : "1.17.6" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "0687f71944021d616d34d922343dcef086855920", + "version" : "600.0.1" + } + } + ], + "version" : 3 +} diff --git a/WireDomain/Package.swift b/WireDomain/Package.swift index a294d3871df..e4dee31641b 100644 --- a/WireDomain/Package.swift +++ b/WireDomain/Package.swift @@ -7,6 +7,7 @@ let package = Package( name: "WireDomainPackage", platforms: [.iOS(.v16), .macOS(.v12)], products: [ + .library(name: "WireDomainAPI", targets: ["WireDomainAPI"]), .library(name: "WireDomainPackage", targets: ["WireDomainPkg"]), .library(name: "WireDomainPackageSupport", targets: ["WireDomainPkgSupport"]) ], @@ -14,12 +15,17 @@ let package = Package( .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), .package(path: "../WirePlugins"), .package(name: "WireAPI", path: "../WireAPI"), + .package(name: "WireAnalytics", path: "../WireAnalytics"), .package(name: "WireFoundation", path: "../WireFoundation") ], targets: [ + .target( + name: "WireDomainAPI", + path: "./Sources/WireDomainAPI" + ), .target( name: "WireDomainPkg", - dependencies: ["WireAPI"], + dependencies: ["WireAPI", "WireAnalytics"], path: "./Sources/Package" ), .target( diff --git a/WireDomain/Project/WireDomain.xctestplan b/WireDomain/Project/TestPlans/AllTests.xctestplan similarity index 67% rename from WireDomain/Project/WireDomain.xctestplan rename to WireDomain/Project/TestPlans/AllTests.xctestplan index 7d9e6118a78..8607d870983 100644 --- a/WireDomain/Project/WireDomain.xctestplan +++ b/WireDomain/Project/TestPlans/AllTests.xctestplan @@ -1,7 +1,7 @@ { "configurations" : [ { - "id" : "7460DDE4-418D-4C73-B293-D9F903D9461E", + "id" : "9D1D0475-9029-44FF-9FE3-BECDF3815511", "name" : "Configuration 1", "options" : { @@ -9,11 +9,9 @@ } ], "defaultOptions" : { - "commandLineArgumentEntries" : [ - { - "argument" : "-com.apple.CoreData.ConcurrencyDebug 1" - } - ] + "language" : "en", + "region" : "DE", + "testExecutionOrdering" : "random" }, "testTargets" : [ { diff --git a/WireDomain/Project/WireDomain Project.xcodeproj/project.pbxproj b/WireDomain/Project/WireDomain Project.xcodeproj/project.pbxproj index c35b277f3a2..b223a391cbc 100644 --- a/WireDomain/Project/WireDomain Project.xcodeproj/project.pbxproj +++ b/WireDomain/Project/WireDomain Project.xcodeproj/project.pbxproj @@ -3,144 +3,26 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ 0163EE812C20D71C00B37260 /* WireDomain.docc in Sources */ = {isa = PBXBuildFile; fileRef = 01D0DCE92C1C8EA10076CB1C /* WireDomain.docc */; }; 017F67942C207AA000B6E02D /* WireDomainSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 017F67822C207A3200B6E02D /* WireDomainSupport.framework */; }; - 017F679C2C20801800B6E02D /* TeamRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017F67982C20801800B6E02D /* TeamRepositoryTests.swift */; }; - 017F67AF2C20803300B6E02D /* AutoMockable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017F67A42C20802300B6E02D /* AutoMockable.generated.swift */; }; 01D0DCB12C1C8C880076CB1C /* WireDomain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01D0DCA62C1C8C870076CB1C /* WireDomain.framework */; }; 01D0DCC62C1C8CD90076CB1C /* WireTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01D0DCC52C1C8CD90076CB1C /* WireTransport.framework */; }; - 162356502C2B223100C6666C /* UserRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1623564F2C2B223100C6666C /* UserRepositoryTests.swift */; }; 591B6E452C8B09BA009F8A7B /* WireDataModel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01D0DCC32C1C8CC20076CB1C /* WireDataModel.framework */; }; 591B6E472C8B09BD009F8A7B /* WireDataModelSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01BDA5442C20762200636E50 /* WireDataModelSupport.framework */; }; + 594904952D0710BF00238104 /* WireAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 594904942D0710BF00238104 /* WireAnalytics */; }; 598D042D2C89C63100B64D71 /* WireFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 598D042C2C89C63100B64D71 /* WireFoundation */; }; 59909A5E2C5BBEA8009C41DE /* WireAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 59909A5D2C5BBEA8009C41DE /* WireAPI */; }; 59909A692C5BC001009C41DE /* WireAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 59909A682C5BC001009C41DE /* WireAPI */; }; 59909A6D2C5BC08B009C41DE /* WireAPISupport in Frameworks */ = {isa = PBXBuildFile; productRef = 59909A6C2C5BC08B009C41DE /* WireAPISupport */; }; - C91F111C2C9C4B2B00BA5BE2 /* ConnectionsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91F111A2C9C4B2B00BA5BE2 /* ConnectionsRepository.swift */; }; - C91F111D2C9C4B2B00BA5BE2 /* ConnectionsLocalStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91F111B2C9C4B2B00BA5BE2 /* ConnectionsLocalStore.swift */; }; - C93961922C91B12800EA971A /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0E117D2C2C4080004BBD29 /* TestError.swift */; }; - C93961932C91B15B00EA971A /* ConversationRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E8A3E72C7F6EA40093DD5C /* ConversationRepositoryTests.swift */; }; - C96B75292CDB7688003A85EB /* ConversationMemberUpdateEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96B75252CDB7688003A85EB /* ConversationMemberUpdateEventProcessorTests.swift */; }; - C96B752A2CDB7688003A85EB /* ConversationMemberJoinEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96B75232CDB7688003A85EB /* ConversationMemberJoinEventTests.swift */; }; - C96B752B2CDB7688003A85EB /* ConversationProtocolUpdateEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96B75262CDB7688003A85EB /* ConversationProtocolUpdateEventProcessorTests.swift */; }; - C96B752C2CDB7688003A85EB /* ConversationAccessUpdateEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96B75212CDB7688003A85EB /* ConversationAccessUpdateEventProcessorTests.swift */; }; - C96B752D2CDB7688003A85EB /* ConversationCreateEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96B75222CDB7688003A85EB /* ConversationCreateEventProcessorTests.swift */; }; - C96B752E2CDB7688003A85EB /* ConversationReceiptModeUpdateEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96B75272CDB7688003A85EB /* ConversationReceiptModeUpdateEventProcessorTests.swift */; }; - C96B752F2CDB7688003A85EB /* ConversationMemberLeaveEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96B75242CDB7688003A85EB /* ConversationMemberLeaveEventTests.swift */; }; - C96B75432CDB8E03003A85EB /* NSManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96B75422CDB8E03003A85EB /* NSManagedObjectContext.swift */; }; - C96B75452CDBA0A9003A85EB /* ConversationDeleteEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96B75442CDBA0A9003A85EB /* ConversationDeleteEventProcessorTests.swift */; }; - C96B75482CDBA10F003A85EB /* SystemMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96B75462CDBA10F003A85EB /* SystemMessage.swift */; }; + 59EA774F2D00CE0C002CA0B8 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59EA774E2D00CE0C002CA0B8 /* WireLogging */; }; + 766BE3C52D036D5000148CB6 /* WireDomainAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 766BE3C42D036D5000148CB6 /* WireDomainAPI */; }; C97BCCAA2C98704B004F2D0D /* WireDomain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01D0DCA62C1C8C870076CB1C /* WireDomain.framework */; }; - C97C013D2CAD7D69000683C5 /* UserPropertiesDeleteEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C013C2CAD7D69000683C5 /* UserPropertiesDeleteEventProcessorTests.swift */; }; - C97C014B2CB00F92000683C5 /* OneOnOneResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C014A2CB00F92000683C5 /* OneOnOneResolver.swift */; }; - C97C01502CB01BDF000683C5 /* OneOnOneResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C014F2CB01BDF000683C5 /* OneOnOneResolverTests.swift */; }; - C97C01522CB01BEF000683C5 /* UserConnectionEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01512CB01BEF000683C5 /* UserConnectionEventProcessorTests.swift */; }; - C97C01542CB04626000683C5 /* UserDeleteEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01532CB04626000683C5 /* UserDeleteEventProcessorTests.swift */; }; - C97C01592CB40010000683C5 /* FederationConnectionRemovedEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01562CB40010000683C5 /* FederationConnectionRemovedEventProcessorTests.swift */; }; - C97C015A2CB40010000683C5 /* FederationDeleteEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01572CB40010000683C5 /* FederationDeleteEventProcessorTests.swift */; }; - C97C015C2CB40038000683C5 /* UserPropertiesSetEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C015B2CB40038000683C5 /* UserPropertiesSetEventProcessorTests.swift */; }; - C97C015F2CB40EF3000683C5 /* PushSupportedProtocolsUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C015E2CB40EF3000683C5 /* PushSupportedProtocolsUseCaseTests.swift */; }; - C97C01A52CB80F54000683C5 /* UserClientsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01A42CB80F4E000683C5 /* UserClientsRepository.swift */; }; - C97C01A82CB813C9000683C5 /* UserClientsRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01A72CB813C9000683C5 /* UserClientsRepositoryTests.swift */; }; - C97C01AC2CB92D47000683C5 /* UserLegalholdEnableEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01AB2CB92D47000683C5 /* UserLegalholdEnableEventProcessorTests.swift */; }; - C97C01BB2CBE5E65000683C5 /* UserUpdateEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01BA2CBE5E65000683C5 /* UserUpdateEventProcessorTests.swift */; }; - C97C01D72CC13E1A000683C5 /* UserLocalStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01D62CC13E1A000683C5 /* UserLocalStore.swift */; }; - C9841A1F2CA309310062A834 /* MockFeatureConfigRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9841A1E2CA309310062A834 /* MockFeatureConfigRepositoryProtocol.swift */; }; - C98433E72CCA868F009723D4 /* MessageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98433E62CCA868F009723D4 /* MessageRepository.swift */; }; - C98433E92CCA86CA009723D4 /* MessageLocalStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98433E82CCA86CA009723D4 /* MessageLocalStore.swift */; }; - C98433EF2CCB7EE6009723D4 /* MessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98433EE2CCB7EE6009723D4 /* MessageType.swift */; }; - C98433F12CCBC251009723D4 /* MessageRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98433F02CCBC251009723D4 /* MessageRepositoryTests.swift */; }; - C98433F52CCBC290009723D4 /* MessageLocalStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98433F42CCBC290009723D4 /* MessageLocalStoreTests.swift */; }; - C98433D02CC26A1D009723D4 /* MLSProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98433CF2CC26A1D009723D4 /* MLSProvider.swift */; }; - C99322D22C986E3A0065E10F /* TeamRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322B22C986E3A0065E10F /* TeamRepository.swift */; }; - C99322D32C986E3A0065E10F /* TeamRepositoryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322B32C986E3A0065E10F /* TeamRepositoryError.swift */; }; - C99322D42C986E3A0065E10F /* SelfUserProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322B52C986E3A0065E10F /* SelfUserProvider.swift */; }; - C99322D52C986E3A0065E10F /* UserModelMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322B62C986E3A0065E10F /* UserModelMappings.swift */; }; - C99322D62C986E3A0065E10F /* UserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322B72C986E3A0065E10F /* UserRepository.swift */; }; - C99322D72C986E3A0065E10F /* UserRepositoryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322B82C986E3A0065E10F /* UserRepositoryError.swift */; }; - C99322D82C986E3A0065E10F /* UpdateEventsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322BA2C986E3A0065E10F /* UpdateEventsRepository.swift */; }; - C99322D92C986E3A0065E10F /* UpdateEventsRepositoryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322BB2C986E3A0065E10F /* UpdateEventsRepositoryError.swift */; }; - C99322DB2C986E3A0065E10F /* FeatureConfigModelMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322BF2C986E3A0065E10F /* FeatureConfigModelMappings.swift */; }; - C99322DC2C986E3A0065E10F /* FeatureConfigRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322C02C986E3A0065E10F /* FeatureConfigRepository.swift */; }; - C99322DD2C986E3A0065E10F /* FeatureConfigRepositoryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322C12C986E3A0065E10F /* FeatureConfigRepositoryError.swift */; }; - C99322DE2C986E3A0065E10F /* ConversationLocalStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322C32C986E3A0065E10F /* ConversationLocalStore.swift */; }; - C99322DF2C986E3A0065E10F /* ConversationLocalStore+Group.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322C42C986E3A0065E10F /* ConversationLocalStore+Group.swift */; }; - C99322E02C986E3A0065E10F /* ConversationLocalStore+Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322C52C986E3A0065E10F /* ConversationLocalStore+Metadata.swift */; }; - C99322E12C986E3A0065E10F /* ConversationLocalStore+MLS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322C62C986E3A0065E10F /* ConversationLocalStore+MLS.swift */; }; - C99322E22C986E3A0065E10F /* ConversationLocalStore+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322C72C986E3A0065E10F /* ConversationLocalStore+Status.swift */; }; - C99322E32C986E3A0065E10F /* ConversationModelMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322C82C986E3A0065E10F /* ConversationModelMappings.swift */; }; - C99322E42C986E3A0065E10F /* ConversationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322C92C986E3A0065E10F /* ConversationRepository.swift */; }; - C99322E52C986E3A0065E10F /* ConversationRepositoryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322CA2C986E3A0065E10F /* ConversationRepositoryError.swift */; }; - C99322E72C986E3A0065E10F /* ConnectionsRepositoryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322CD2C986E3A0065E10F /* ConnectionsRepositoryError.swift */; }; - C99322E82C986E3A0065E10F /* ConversationLabelsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322CF2C986E3A0065E10F /* ConversationLabelsRepository.swift */; }; - C99322E92C986E3A0065E10F /* ConversationLabelsRepositoryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322D02C986E3A0065E10F /* ConversationLabelsRepositoryError.swift */; }; - C9C8FDCE2C9DBE0E00702B91 /* FeatureConfigUpdateEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9C8FDC32C9DBE0E00702B91 /* FeatureConfigUpdateEventProcessorTests.swift */; }; - C9C8FDCF2C9DBE0E00702B91 /* TeamDeleteEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9C8FDC52C9DBE0E00702B91 /* TeamDeleteEventProcessorTests.swift */; }; - C9C8FDD02C9DBE0E00702B91 /* TeamMemberLeaveEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9C8FDC62C9DBE0E00702B91 /* TeamMemberLeaveEventProcessorTests.swift */; }; - C9C8FDD12C9DBE0E00702B91 /* TeamMemberUpdateEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9C8FDC72C9DBE0E00702B91 /* TeamMemberUpdateEventProcessorTests.swift */; }; - C9C8FDD22C9DBE0E00702B91 /* UserClientAddEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9C8FDC92C9DBE0E00702B91 /* UserClientAddEventProcessorTests.swift */; }; - C9C8FDD32C9DBE0E00702B91 /* UserLegalHoldDisableEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9C8FDCA2C9DBE0E00702B91 /* UserLegalHoldDisableEventProcessorTests.swift */; }; - C9C8FDD42C9DBE0E00702B91 /* UserLegalholdRequestEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9C8FDCB2C9DBE0E00702B91 /* UserLegalholdRequestEventProcessorTests.swift */; }; C9E0C9BB2C91B76F00CE6607 /* WireTestingPackage in Frameworks */ = {isa = PBXBuildFile; productRef = C9E0C9BA2C91B76F00CE6607 /* WireTestingPackage */; }; - C9E8A3AE2C73878B0093DD5C /* ConnectionsRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E8A3AD2C73878B0093DD5C /* ConnectionsRepositoryTests.swift */; }; - C9E8A3B72C749F2A0093DD5C /* ConversationLabelsRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E8A3B62C749F2A0093DD5C /* ConversationLabelsRepositoryTests.swift */; }; - C9E8A3C02C761EDD0093DD5C /* FeatureConfigRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E8A3BF2C761EDD0093DD5C /* FeatureConfigRepositoryTests.swift */; }; - C9EA769F2C92DD0F00A7D35C /* PushSupportedProtocolsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EA769E2C92DD0F00A7D35C /* PushSupportedProtocolsUseCase.swift */; }; - C9F691292C9B164A008CC41F /* UserPushRemoveEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F691282C9B164A008CC41F /* UserPushRemoveEventProcessorTests.swift */; }; - C9FDF3EC2CAA988900D78098 /* ConnectionsModelMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FDF3EA2CAA988100D78098 /* ConnectionsModelMappings.swift */; }; CB7979132C738508006FBA58 /* WireTransportSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB7979122C738508006FBA58 /* WireTransportSupport.framework */; }; - CB7979162C738547006FBA58 /* TestSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7979152C738547006FBA58 /* TestSetup.swift */; }; - EE368CCE2C2DAA87009DBAB0 /* ConversationEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE368CC72C2DAA87009DBAB0 /* ConversationEventProcessor.swift */; }; - EE368CCF2C2DAA87009DBAB0 /* FeatureConfigEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE368CC82C2DAA87009DBAB0 /* FeatureConfigEventProcessor.swift */; }; - EE368CD02C2DAA87009DBAB0 /* FederationEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE368CC92C2DAA87009DBAB0 /* FederationEventProcessor.swift */; }; - EE368CD12C2DAA87009DBAB0 /* TeamEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE368CCA2C2DAA87009DBAB0 /* TeamEventProcessor.swift */; }; - EE368CD22C2DAA87009DBAB0 /* UpdateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE368CCB2C2DAA87009DBAB0 /* UpdateEventProcessor.swift */; }; - EE368CD32C2DAA87009DBAB0 /* UserEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE368CCC2C2DAA87009DBAB0 /* UserEventProcessor.swift */; }; - EE3F97542C2ADC4C00668DF1 /* ProteusMessageDecryptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3F97532C2ADC4C00668DF1 /* ProteusMessageDecryptorTests.swift */; }; - EE57A6FE2C298F380096F242 /* UpdateEventDecryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE57A6FD2C298F380096F242 /* UpdateEventDecryptor.swift */; }; - EE57A7002C298F630096F242 /* ProteusMessageDecryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE57A6FF2C298F630096F242 /* ProteusMessageDecryptor.swift */; }; - EE57A7032C2994420096F242 /* UpdateEventsRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE57A7022C2994420096F242 /* UpdateEventsRepositoryTests.swift */; }; - EE57A7082C2A8B740096F242 /* ProteusMessageDecryptorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE57A7062C2A86880096F242 /* ProteusMessageDecryptorError.swift */; }; - EE57A70B2C2A8BAA0096F242 /* UpdateEventDecryptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE57A70A2C2A8BAA0096F242 /* UpdateEventDecryptorTests.swift */; }; - EEAD09F22C46604400CC8658 /* ConversationAccessUpdateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD09F12C46604400CC8658 /* ConversationAccessUpdateEventProcessor.swift */; }; - EEAD09F42C46709800CC8658 /* ConversationCodeUpdateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD09F32C46709800CC8658 /* ConversationCodeUpdateEventProcessor.swift */; }; - EEAD09F62C46772000CC8658 /* ConversationCreateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD09F52C46772000CC8658 /* ConversationCreateEventProcessor.swift */; }; - EEAD09F82C46772C00CC8658 /* ConversationDeleteEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD09F72C46772C00CC8658 /* ConversationDeleteEventProcessor.swift */; }; - EEAD09FA2C46773300CC8658 /* ConversationMemberJoinEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD09F92C46773300CC8658 /* ConversationMemberJoinEventProcessor.swift */; }; - EEAD09FC2C46773900CC8658 /* ConversationMemberLeaveEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD09FB2C46773900CC8658 /* ConversationMemberLeaveEventProcessor.swift */; }; - EEAD09FE2C46774200CC8658 /* ConversationMemberUpdateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD09FD2C46774200CC8658 /* ConversationMemberUpdateEventProcessor.swift */; }; - EEAD0A002C46774900CC8658 /* ConversationMessageTimerUpdateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD09FF2C46774900CC8658 /* ConversationMessageTimerUpdateEventProcessor.swift */; }; - EEAD0A022C46774F00CC8658 /* ConversationMLSMessageAddEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A012C46774F00CC8658 /* ConversationMLSMessageAddEventProcessor.swift */; }; - EEAD0A042C46775700CC8658 /* ConversationMLSWelcomeEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A032C46775700CC8658 /* ConversationMLSWelcomeEventProcessor.swift */; }; - EEAD0A062C46775D00CC8658 /* ConversationProteusMessageAddEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A052C46775D00CC8658 /* ConversationProteusMessageAddEventProcessor.swift */; }; - EEAD0A082C46776400CC8658 /* ConversationProtocolUpdateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A072C46776400CC8658 /* ConversationProtocolUpdateEventProcessor.swift */; }; - EEAD0A0A2C46776B00CC8658 /* ConversationReceiptModeUpdateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A092C46776B00CC8658 /* ConversationReceiptModeUpdateEventProcessor.swift */; }; - EEAD0A0C2C46777200CC8658 /* ConversationRenameEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A0B2C46777200CC8658 /* ConversationRenameEventProcessor.swift */; }; - EEAD0A0E2C46777800CC8658 /* ConversationTypingEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A0D2C46777800CC8658 /* ConversationTypingEventProcessor.swift */; }; - EEAD0A112C46A33E00CC8658 /* TeamMemberUpdateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A102C46A33E00CC8658 /* TeamMemberUpdateEventProcessor.swift */; }; - EEAD0A132C46A38200CC8658 /* TeamMemberLeaveEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A122C46A38200CC8658 /* TeamMemberLeaveEventProcessor.swift */; }; - EEAD0A152C46A3AB00CC8658 /* TeamDeleteEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A142C46A3AB00CC8658 /* TeamDeleteEventProcessor.swift */; }; - EEAD0A182C46A88D00CC8658 /* UserClientAddEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A172C46A88D00CC8658 /* UserClientAddEventProcessor.swift */; }; - EEAD0A1A2C46A92000CC8658 /* UserClientRemoveEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A192C46A92000CC8658 /* UserClientRemoveEventProcessor.swift */; }; - EEAD0A1C2C46A99D00CC8658 /* UserConnectionEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A1B2C46A99D00CC8658 /* UserConnectionEventProcessor.swift */; }; - EEAD0A202C46AB8700CC8658 /* UserDeleteEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A1F2C46AB8700CC8658 /* UserDeleteEventProcessor.swift */; }; - EEAD0A222C46ABFB00CC8658 /* UserLegalholdDisableEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A212C46ABFA00CC8658 /* UserLegalholdDisableEventProcessor.swift */; }; - EEAD0A242C46ACEA00CC8658 /* UserLegalholdEnableEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A232C46ACEA00CC8658 /* UserLegalholdEnableEventProcessor.swift */; }; - EEAD0A262C46AD4D00CC8658 /* UserLegalholdRequestEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A252C46AD4D00CC8658 /* UserLegalholdRequestEventProcessor.swift */; }; - EEAD0A282C46AE6400CC8658 /* UserPropertiesSetEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A272C46AE6400CC8658 /* UserPropertiesSetEventProcessor.swift */; }; - EEAD0A2A2C46AEB600CC8658 /* UserPropertiesDeleteEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A292C46AEB600CC8658 /* UserPropertiesDeleteEventProcessor.swift */; }; - EEAD0A2C2C46AF9200CC8658 /* UserPushRemoveEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A2B2C46AF9200CC8658 /* UserPushRemoveEventProcessor.swift */; }; - EEAD0A2E2C46B01900CC8658 /* UserUpdateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A2D2C46B01900CC8658 /* UserUpdateEventProcessor.swift */; }; - EEAD0A312C46B98D00CC8658 /* FederationConnectionRemovedEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A302C46B98D00CC8658 /* FederationConnectionRemovedEventProcessor.swift */; }; - EEAD0A332C46B99800CC8658 /* FederationDeleteEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A322C46B99800CC8658 /* FederationDeleteEventProcessor.swift */; }; - EEAD0A362C46BBA600CC8658 /* FeatureConfigUpdateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD0A352C46BBA600CC8658 /* FeatureConfigUpdateEventProcessor.swift */; }; - EEC410262C60D48900E89394 /* SyncManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC410242C60D48900E89394 /* SyncManagerTests.swift */; }; - EECC35A62C2EB6CD00679448 /* SyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECC35A52C2EB6CD00679448 /* SyncManager.swift */; }; - EECC35A82C2EB70400679448 /* SyncState.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECC35A72C2EB70400679448 /* SyncState.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -162,139 +44,32 @@ /* Begin PBXFileReference section */ 017F67822C207A3200B6E02D /* WireDomainSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WireDomainSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 017F67982C20801800B6E02D /* TeamRepositoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamRepositoryTests.swift; sourceTree = ""; }; - 017F67A42C20802300B6E02D /* AutoMockable.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoMockable.generated.swift; sourceTree = ""; }; - 017F67A62C20802300B6E02D /* AutoMockable.stencil */ = {isa = PBXFileReference; lastKnownFileType = text; path = AutoMockable.stencil; sourceTree = ""; }; - 017F67A72C20802300B6E02D /* config.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = config.yml; sourceTree = ""; }; 01BDA5442C20762200636E50 /* WireDataModelSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireDataModelSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 01D0DCA62C1C8C870076CB1C /* WireDomain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WireDomain.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 01D0DCB02C1C8C880076CB1C /* WireDomainTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WireDomainTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 01D0DCC32C1C8CC20076CB1C /* WireDataModel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireDataModel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 01D0DCC52C1C8CD90076CB1C /* WireTransport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireTransport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 01D0DCE92C1C8EA10076CB1C /* WireDomain.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = WireDomain.docc; sourceTree = ""; }; - 01D0DCFC2C1C8F9B0076CB1C /* WireDomain.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = WireDomain.xctestplan; sourceTree = ""; }; - 1623564F2C2B223100C6666C /* UserRepositoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserRepositoryTests.swift; sourceTree = ""; }; - C91F111A2C9C4B2B00BA5BE2 /* ConnectionsRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionsRepository.swift; sourceTree = ""; }; - C91F111B2C9C4B2B00BA5BE2 /* ConnectionsLocalStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionsLocalStore.swift; sourceTree = ""; }; - C96B75212CDB7688003A85EB /* ConversationAccessUpdateEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationAccessUpdateEventProcessorTests.swift; sourceTree = ""; }; - C96B75222CDB7688003A85EB /* ConversationCreateEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationCreateEventProcessorTests.swift; sourceTree = ""; }; - C96B75232CDB7688003A85EB /* ConversationMemberJoinEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMemberJoinEventTests.swift; sourceTree = ""; }; - C96B75242CDB7688003A85EB /* ConversationMemberLeaveEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMemberLeaveEventTests.swift; sourceTree = ""; }; - C96B75252CDB7688003A85EB /* ConversationMemberUpdateEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMemberUpdateEventProcessorTests.swift; sourceTree = ""; }; - C96B75262CDB7688003A85EB /* ConversationProtocolUpdateEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationProtocolUpdateEventProcessorTests.swift; sourceTree = ""; }; - C96B75272CDB7688003A85EB /* ConversationReceiptModeUpdateEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationReceiptModeUpdateEventProcessorTests.swift; sourceTree = ""; }; - C96B75422CDB8E03003A85EB /* NSManagedObjectContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSManagedObjectContext.swift; sourceTree = ""; }; - C96B75442CDBA0A9003A85EB /* ConversationDeleteEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationDeleteEventProcessorTests.swift; sourceTree = ""; }; - C96B75462CDBA10F003A85EB /* SystemMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemMessage.swift; sourceTree = ""; }; - C97C013C2CAD7D69000683C5 /* UserPropertiesDeleteEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPropertiesDeleteEventProcessorTests.swift; sourceTree = ""; }; - C97C014A2CB00F92000683C5 /* OneOnOneResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OneOnOneResolver.swift; path = ../../OneOnOneResolver.swift; sourceTree = ""; }; - C97C014F2CB01BDF000683C5 /* OneOnOneResolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneOnOneResolverTests.swift; sourceTree = ""; }; - C97C01512CB01BEF000683C5 /* UserConnectionEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConnectionEventProcessorTests.swift; sourceTree = ""; }; - C97C01532CB04626000683C5 /* UserDeleteEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDeleteEventProcessorTests.swift; sourceTree = ""; }; - C97C01562CB40010000683C5 /* FederationConnectionRemovedEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederationConnectionRemovedEventProcessorTests.swift; sourceTree = ""; }; - C97C01572CB40010000683C5 /* FederationDeleteEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederationDeleteEventProcessorTests.swift; sourceTree = ""; }; - C97C015B2CB40038000683C5 /* UserPropertiesSetEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPropertiesSetEventProcessorTests.swift; sourceTree = ""; }; - C97C015E2CB40EF3000683C5 /* PushSupportedProtocolsUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushSupportedProtocolsUseCaseTests.swift; sourceTree = ""; }; - C97C01A42CB80F4E000683C5 /* UserClientsRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserClientsRepository.swift; sourceTree = ""; }; - C97C01A72CB813C9000683C5 /* UserClientsRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserClientsRepositoryTests.swift; sourceTree = ""; }; - C97C01AB2CB92D47000683C5 /* UserLegalholdEnableEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLegalholdEnableEventProcessorTests.swift; sourceTree = ""; }; - C97C01BA2CBE5E65000683C5 /* UserUpdateEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserUpdateEventProcessorTests.swift; sourceTree = ""; }; - C97C01D62CC13E1A000683C5 /* UserLocalStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLocalStore.swift; sourceTree = ""; }; - C9841A1E2CA309310062A834 /* MockFeatureConfigRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFeatureConfigRepositoryProtocol.swift; sourceTree = ""; }; - C98433E62CCA868F009723D4 /* MessageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRepository.swift; sourceTree = ""; }; - C98433E82CCA86CA009723D4 /* MessageLocalStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLocalStore.swift; sourceTree = ""; }; - C98433EE2CCB7EE6009723D4 /* MessageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageType.swift; sourceTree = ""; }; - C98433F02CCBC251009723D4 /* MessageRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRepositoryTests.swift; sourceTree = ""; }; - C98433F42CCBC290009723D4 /* MessageLocalStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLocalStoreTests.swift; sourceTree = ""; }; - C98433CF2CC26A1D009723D4 /* MLSProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLSProvider.swift; sourceTree = ""; }; - C99322B22C986E3A0065E10F /* TeamRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamRepository.swift; sourceTree = ""; }; - C99322B32C986E3A0065E10F /* TeamRepositoryError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamRepositoryError.swift; sourceTree = ""; }; - C99322B52C986E3A0065E10F /* SelfUserProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelfUserProvider.swift; sourceTree = ""; }; - C99322B62C986E3A0065E10F /* UserModelMappings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserModelMappings.swift; sourceTree = ""; }; - C99322B72C986E3A0065E10F /* UserRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserRepository.swift; sourceTree = ""; }; - C99322B82C986E3A0065E10F /* UserRepositoryError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserRepositoryError.swift; sourceTree = ""; }; - C99322BA2C986E3A0065E10F /* UpdateEventsRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateEventsRepository.swift; sourceTree = ""; }; - C99322BB2C986E3A0065E10F /* UpdateEventsRepositoryError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateEventsRepositoryError.swift; sourceTree = ""; }; - C99322BF2C986E3A0065E10F /* FeatureConfigModelMappings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureConfigModelMappings.swift; sourceTree = ""; }; - C99322C02C986E3A0065E10F /* FeatureConfigRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureConfigRepository.swift; sourceTree = ""; }; - C99322C12C986E3A0065E10F /* FeatureConfigRepositoryError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureConfigRepositoryError.swift; sourceTree = ""; }; - C99322C32C986E3A0065E10F /* ConversationLocalStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationLocalStore.swift; sourceTree = ""; }; - C99322C42C986E3A0065E10F /* ConversationLocalStore+Group.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConversationLocalStore+Group.swift"; sourceTree = ""; }; - C99322C52C986E3A0065E10F /* ConversationLocalStore+Metadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConversationLocalStore+Metadata.swift"; sourceTree = ""; }; - C99322C62C986E3A0065E10F /* ConversationLocalStore+MLS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConversationLocalStore+MLS.swift"; sourceTree = ""; }; - C99322C72C986E3A0065E10F /* ConversationLocalStore+Status.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConversationLocalStore+Status.swift"; sourceTree = ""; }; - C99322C82C986E3A0065E10F /* ConversationModelMappings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationModelMappings.swift; sourceTree = ""; }; - C99322C92C986E3A0065E10F /* ConversationRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationRepository.swift; sourceTree = ""; }; - C99322CA2C986E3A0065E10F /* ConversationRepositoryError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationRepositoryError.swift; sourceTree = ""; }; - C99322CD2C986E3A0065E10F /* ConnectionsRepositoryError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionsRepositoryError.swift; sourceTree = ""; }; - C99322CF2C986E3A0065E10F /* ConversationLabelsRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationLabelsRepository.swift; sourceTree = ""; }; - C99322D02C986E3A0065E10F /* ConversationLabelsRepositoryError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationLabelsRepositoryError.swift; sourceTree = ""; }; - C9C8FDC32C9DBE0E00702B91 /* FeatureConfigUpdateEventProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureConfigUpdateEventProcessorTests.swift; sourceTree = ""; }; - C9C8FDC52C9DBE0E00702B91 /* TeamDeleteEventProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamDeleteEventProcessorTests.swift; sourceTree = ""; }; - C9C8FDC62C9DBE0E00702B91 /* TeamMemberLeaveEventProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamMemberLeaveEventProcessorTests.swift; sourceTree = ""; }; - C9C8FDC72C9DBE0E00702B91 /* TeamMemberUpdateEventProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamMemberUpdateEventProcessorTests.swift; sourceTree = ""; }; - C9C8FDC92C9DBE0E00702B91 /* UserClientAddEventProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserClientAddEventProcessorTests.swift; sourceTree = ""; }; - C9C8FDCA2C9DBE0E00702B91 /* UserLegalHoldDisableEventProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserLegalHoldDisableEventProcessorTests.swift; sourceTree = ""; }; - C9C8FDCB2C9DBE0E00702B91 /* UserLegalholdRequestEventProcessorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserLegalholdRequestEventProcessorTests.swift; sourceTree = ""; }; - C9E8A3AD2C73878B0093DD5C /* ConnectionsRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsRepositoryTests.swift; sourceTree = ""; }; - C9E8A3B62C749F2A0093DD5C /* ConversationLabelsRepositoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationLabelsRepositoryTests.swift; sourceTree = ""; }; - C9E8A3BF2C761EDD0093DD5C /* FeatureConfigRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureConfigRepositoryTests.swift; sourceTree = ""; }; - C9E8A3E72C7F6EA40093DD5C /* ConversationRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationRepositoryTests.swift; sourceTree = ""; }; - C9EA769E2C92DD0F00A7D35C /* PushSupportedProtocolsUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushSupportedProtocolsUseCase.swift; sourceTree = ""; }; - C9F691282C9B164A008CC41F /* UserPushRemoveEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPushRemoveEventProcessorTests.swift; sourceTree = ""; }; - C9FDF3EA2CAA988100D78098 /* ConnectionsModelMappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsModelMappings.swift; sourceTree = ""; }; CB7979122C738508006FBA58 /* WireTransportSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireTransportSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - CB7979152C738547006FBA58 /* TestSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSetup.swift; sourceTree = ""; }; - EE0E117D2C2C4080004BBD29 /* TestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestError.swift; sourceTree = ""; }; - EE368CC72C2DAA87009DBAB0 /* ConversationEventProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationEventProcessor.swift; sourceTree = ""; }; - EE368CC82C2DAA87009DBAB0 /* FeatureConfigEventProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureConfigEventProcessor.swift; sourceTree = ""; }; - EE368CC92C2DAA87009DBAB0 /* FederationEventProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FederationEventProcessor.swift; sourceTree = ""; }; - EE368CCA2C2DAA87009DBAB0 /* TeamEventProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamEventProcessor.swift; sourceTree = ""; }; - EE368CCB2C2DAA87009DBAB0 /* UpdateEventProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateEventProcessor.swift; sourceTree = ""; }; - EE368CCC2C2DAA87009DBAB0 /* UserEventProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserEventProcessor.swift; sourceTree = ""; }; - EE3F97532C2ADC4C00668DF1 /* ProteusMessageDecryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProteusMessageDecryptorTests.swift; sourceTree = ""; }; - EE57A6FD2C298F380096F242 /* UpdateEventDecryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateEventDecryptor.swift; sourceTree = ""; }; - EE57A6FF2C298F630096F242 /* ProteusMessageDecryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProteusMessageDecryptor.swift; sourceTree = ""; }; - EE57A7022C2994420096F242 /* UpdateEventsRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateEventsRepositoryTests.swift; sourceTree = ""; }; - EE57A7062C2A86880096F242 /* ProteusMessageDecryptorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProteusMessageDecryptorError.swift; sourceTree = ""; }; - EE57A70A2C2A8BAA0096F242 /* UpdateEventDecryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateEventDecryptorTests.swift; sourceTree = ""; }; - EEAD09F12C46604400CC8658 /* ConversationAccessUpdateEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationAccessUpdateEventProcessor.swift; sourceTree = ""; }; - EEAD09F32C46709800CC8658 /* ConversationCodeUpdateEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationCodeUpdateEventProcessor.swift; sourceTree = ""; }; - EEAD09F52C46772000CC8658 /* ConversationCreateEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationCreateEventProcessor.swift; sourceTree = ""; }; - EEAD09F72C46772C00CC8658 /* ConversationDeleteEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationDeleteEventProcessor.swift; sourceTree = ""; }; - EEAD09F92C46773300CC8658 /* ConversationMemberJoinEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMemberJoinEventProcessor.swift; sourceTree = ""; }; - EEAD09FB2C46773900CC8658 /* ConversationMemberLeaveEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMemberLeaveEventProcessor.swift; sourceTree = ""; }; - EEAD09FD2C46774200CC8658 /* ConversationMemberUpdateEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMemberUpdateEventProcessor.swift; sourceTree = ""; }; - EEAD09FF2C46774900CC8658 /* ConversationMessageTimerUpdateEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMessageTimerUpdateEventProcessor.swift; sourceTree = ""; }; - EEAD0A012C46774F00CC8658 /* ConversationMLSMessageAddEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMLSMessageAddEventProcessor.swift; sourceTree = ""; }; - EEAD0A032C46775700CC8658 /* ConversationMLSWelcomeEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMLSWelcomeEventProcessor.swift; sourceTree = ""; }; - EEAD0A052C46775D00CC8658 /* ConversationProteusMessageAddEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationProteusMessageAddEventProcessor.swift; sourceTree = ""; }; - EEAD0A072C46776400CC8658 /* ConversationProtocolUpdateEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationProtocolUpdateEventProcessor.swift; sourceTree = ""; }; - EEAD0A092C46776B00CC8658 /* ConversationReceiptModeUpdateEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationReceiptModeUpdateEventProcessor.swift; sourceTree = ""; }; - EEAD0A0B2C46777200CC8658 /* ConversationRenameEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationRenameEventProcessor.swift; sourceTree = ""; }; - EEAD0A0D2C46777800CC8658 /* ConversationTypingEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTypingEventProcessor.swift; sourceTree = ""; }; - EEAD0A102C46A33E00CC8658 /* TeamMemberUpdateEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamMemberUpdateEventProcessor.swift; sourceTree = ""; }; - EEAD0A122C46A38200CC8658 /* TeamMemberLeaveEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamMemberLeaveEventProcessor.swift; sourceTree = ""; }; - EEAD0A142C46A3AB00CC8658 /* TeamDeleteEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamDeleteEventProcessor.swift; sourceTree = ""; }; - EEAD0A172C46A88D00CC8658 /* UserClientAddEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserClientAddEventProcessor.swift; sourceTree = ""; }; - EEAD0A192C46A92000CC8658 /* UserClientRemoveEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserClientRemoveEventProcessor.swift; sourceTree = ""; }; - EEAD0A1B2C46A99D00CC8658 /* UserConnectionEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConnectionEventProcessor.swift; sourceTree = ""; }; - EEAD0A1F2C46AB8700CC8658 /* UserDeleteEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDeleteEventProcessor.swift; sourceTree = ""; }; - EEAD0A212C46ABFA00CC8658 /* UserLegalholdDisableEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLegalholdDisableEventProcessor.swift; sourceTree = ""; }; - EEAD0A232C46ACEA00CC8658 /* UserLegalholdEnableEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLegalholdEnableEventProcessor.swift; sourceTree = ""; }; - EEAD0A252C46AD4D00CC8658 /* UserLegalholdRequestEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLegalholdRequestEventProcessor.swift; sourceTree = ""; }; - EEAD0A272C46AE6400CC8658 /* UserPropertiesSetEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPropertiesSetEventProcessor.swift; sourceTree = ""; }; - EEAD0A292C46AEB600CC8658 /* UserPropertiesDeleteEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPropertiesDeleteEventProcessor.swift; sourceTree = ""; }; - EEAD0A2B2C46AF9200CC8658 /* UserPushRemoveEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPushRemoveEventProcessor.swift; sourceTree = ""; }; - EEAD0A2D2C46B01900CC8658 /* UserUpdateEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserUpdateEventProcessor.swift; sourceTree = ""; }; - EEAD0A302C46B98D00CC8658 /* FederationConnectionRemovedEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederationConnectionRemovedEventProcessor.swift; sourceTree = ""; }; - EEAD0A322C46B99800CC8658 /* FederationDeleteEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederationDeleteEventProcessor.swift; sourceTree = ""; }; - EEAD0A352C46BBA600CC8658 /* FeatureConfigUpdateEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureConfigUpdateEventProcessor.swift; sourceTree = ""; }; - EEC410242C60D48900E89394 /* SyncManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncManagerTests.swift; sourceTree = ""; }; - EECC35A52C2EB6CD00679448 /* SyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncManager.swift; sourceTree = ""; }; - EECC35A72C2EB70400679448 /* SyncState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncState.swift; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 59EA78D62D00CF22002CA0B8 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Sourcery/generated/AutoMockable.generated.swift, + ); + target = 017F67812C207A3200B6E02D /* WireDomainSupport */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 59EA76E82D00CD88002CA0B8 /* TestPlans */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = TestPlans; sourceTree = ""; }; + 59EA78992D00CF1C002CA0B8 /* WireDomainTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = WireDomainTests; sourceTree = ""; }; + 59EA78D42D00CF22002CA0B8 /* WireDomainSupport */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (59EA78D62D00CF22002CA0B8 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = WireDomainSupport; sourceTree = ""; }; + 59EA7A282D00CFB2002CA0B8 /* WireDomain */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = WireDomain; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 017F677F2C207A3200B6E02D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -309,8 +84,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 766BE3C52D036D5000148CB6 /* WireDomainAPI in Frameworks */, 598D042D2C89C63100B64D71 /* WireFoundation in Frameworks */, 591B6E452C8B09BA009F8A7B /* WireDataModel.framework in Frameworks */, + 594904952D0710BF00238104 /* WireAnalytics in Frameworks */, + 59EA774F2D00CE0C002CA0B8 /* WireLogging in Frameworks */, 59909A5E2C5BBEA8009C41DE /* WireAPI in Frameworks */, 01D0DCC62C1C8CD90076CB1C /* WireTransport.framework in Frameworks */, ); @@ -332,97 +110,9 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 017F67992C20801800B6E02D /* Repositories */ = { - isa = PBXGroup; - children = ( - C9841A1D2CA309090062A834 /* Mock */, - C9E8A3B62C749F2A0093DD5C /* ConversationLabelsRepositoryTests.swift */, - C9E8A3AD2C73878B0093DD5C /* ConnectionsRepositoryTests.swift */, - 017F67982C20801800B6E02D /* TeamRepositoryTests.swift */, - EE57A7022C2994420096F242 /* UpdateEventsRepositoryTests.swift */, - C9E8A3E72C7F6EA40093DD5C /* ConversationRepositoryTests.swift */, - C9E8A3BF2C761EDD0093DD5C /* FeatureConfigRepositoryTests.swift */, - 1623564F2C2B223100C6666C /* UserRepositoryTests.swift */, - C98433F02CCBC251009723D4 /* MessageRepositoryTests.swift */, - C97C01A72CB813C9000683C5 /* UserClientsRepositoryTests.swift */, - ); - path = Repositories; - sourceTree = ""; - }; - 017F679A2C20801800B6E02D /* WireDomain */ = { - isa = PBXGroup; - children = ( - C97C015D2CB40EBE000683C5 /* UseCases */, - C9C8FDCD2C9DBE0E00702B91 /* Event Processing */, - EEC410252C60D48900E89394 /* Synchronization */, - EE0E117C2C2C4076004BBD29 /* Helpers */, - EE57A7092C2A8B950096F242 /* Event Decryption */, - 017F67992C20801800B6E02D /* Repositories */, - C98433F32CCBC26E009723D4 /* LocalStores */, - ); - name = WireDomain; - path = WireDomainTests; - sourceTree = ""; - }; - 017F67A22C20802300B6E02D /* Repositories */ = { - isa = PBXGroup; - children = ( - C98433E52CCA867F009723D4 /* Message */, - C97C01A32CB80F46000683C5 /* UserClients */, - C99322CE2C986E3A0065E10F /* Connections */, - C99322CB2C986E3A0065E10F /* Conversations */, - C99322D12C986E3A0065E10F /* ConversationsLabels */, - C99322C22C986E3A0065E10F /* FeatureConfig */, - C99322B42C986E3A0065E10F /* Team */, - C99322BC2C986E3A0065E10F /* UpdateEvents */, - C99322B92C986E3A0065E10F /* User */, - ); - path = Repositories; - sourceTree = ""; - }; - 017F67A32C20802300B6E02D /* WireDomain */ = { - isa = PBXGroup; - children = ( - C97C01C02CBE9C93000683C5 /* Extensions */, - C9EA76AD2C98548900A7D35C /* UseCases */, - EECC35A42C2EB6C100679448 /* Synchronization */, - EE57A6FC2C298F280096F242 /* Event Decryption */, - EE368CCD2C2DAA87009DBAB0 /* Event Processing */, - 017F67A22C20802300B6E02D /* Repositories */, - ); - path = WireDomain; - sourceTree = ""; - }; - 017F67A52C20802300B6E02D /* generated */ = { - isa = PBXGroup; - children = ( - 017F67A42C20802300B6E02D /* AutoMockable.generated.swift */, - ); - path = generated; - sourceTree = ""; - }; - 017F67A82C20802300B6E02D /* Sourcery */ = { - isa = PBXGroup; - children = ( - 017F67A52C20802300B6E02D /* generated */, - 017F67A62C20802300B6E02D /* AutoMockable.stencil */, - 017F67A72C20802300B6E02D /* config.yml */, - ); - path = Sourcery; - sourceTree = ""; - }; - 017F67A92C20802300B6E02D /* WireDomainSupport */ = { - isa = PBXGroup; - children = ( - 017F67A82C20802300B6E02D /* Sourcery */, - ); - path = WireDomainSupport; - sourceTree = ""; - }; 01D0DC9C2C1C8C870076CB1C = { isa = PBXGroup; children = ( - 01D0DCFC2C1C8F9B0076CB1C /* WireDomain.xctestplan */, 01D0DCE82C1C8EA10076CB1C /* Sources */, 01D0DCED2C1C8EA10076CB1C /* Tests */, 01D0DCE92C1C8EA10076CB1C /* WireDomain.docc */, @@ -455,8 +145,8 @@ 01D0DCE82C1C8EA10076CB1C /* Sources */ = { isa = PBXGroup; children = ( - 017F67A32C20802300B6E02D /* WireDomain */, - 017F67A92C20802300B6E02D /* WireDomainSupport */, + 59EA7A282D00CFB2002CA0B8 /* WireDomain */, + 59EA78D42D00CF22002CA0B8 /* WireDomainSupport */, ); name = Sources; path = ../Sources; @@ -465,374 +155,13 @@ 01D0DCED2C1C8EA10076CB1C /* Tests */ = { isa = PBXGroup; children = ( - 017F679A2C20801800B6E02D /* WireDomain */, + 59EA76E82D00CD88002CA0B8 /* TestPlans */, + 59EA78992D00CF1C002CA0B8 /* WireDomainTests */, ); name = Tests; path = ../Tests; sourceTree = ""; }; - C96B75282CDB7688003A85EB /* ConversationEventProcessor */ = { - isa = PBXGroup; - children = ( - C96B75212CDB7688003A85EB /* ConversationAccessUpdateEventProcessorTests.swift */, - C96B75222CDB7688003A85EB /* ConversationCreateEventProcessorTests.swift */, - C96B75442CDBA0A9003A85EB /* ConversationDeleteEventProcessorTests.swift */, - C96B75232CDB7688003A85EB /* ConversationMemberJoinEventTests.swift */, - C96B75242CDB7688003A85EB /* ConversationMemberLeaveEventTests.swift */, - C96B75252CDB7688003A85EB /* ConversationMemberUpdateEventProcessorTests.swift */, - C96B75262CDB7688003A85EB /* ConversationProtocolUpdateEventProcessorTests.swift */, - C96B75272CDB7688003A85EB /* ConversationReceiptModeUpdateEventProcessorTests.swift */, - ); - path = ConversationEventProcessor; - sourceTree = ""; - }; - C96B75472CDBA10F003A85EB /* Models */ = { - isa = PBXGroup; - children = ( - C96B75462CDBA10F003A85EB /* SystemMessage.swift */, - ); - path = Models; - sourceTree = ""; - }; - C97C01582CB40010000683C5 /* FederationEventProcessor */ = { - isa = PBXGroup; - children = ( - C97C01562CB40010000683C5 /* FederationConnectionRemovedEventProcessorTests.swift */, - C97C01572CB40010000683C5 /* FederationDeleteEventProcessorTests.swift */, - ); - path = FederationEventProcessor; - sourceTree = ""; - }; - C97C015D2CB40EBE000683C5 /* UseCases */ = { - isa = PBXGroup; - children = ( - C97C015E2CB40EF3000683C5 /* PushSupportedProtocolsUseCaseTests.swift */, - ); - path = UseCases; - sourceTree = ""; - }; - C97C01A32CB80F46000683C5 /* UserClients */ = { - isa = PBXGroup; - children = ( - C97C01A42CB80F4E000683C5 /* UserClientsRepository.swift */, - ); - path = UserClients; - sourceTree = ""; - }; - C97C01C02CBE9C93000683C5 /* Extensions */ = { - isa = PBXGroup; - children = ( - C97C01C12CBE9C9B000683C5 /* CoreData */, - ); - path = Extensions; - sourceTree = ""; - }; - C97C01C12CBE9C9B000683C5 /* CoreData */ = { - isa = PBXGroup; - children = ( - C96B75422CDB8E03003A85EB /* NSManagedObjectContext.swift */, - ); - path = CoreData; - sourceTree = ""; - }; - C9841A1D2CA309090062A834 /* Mock */ = { - isa = PBXGroup; - children = ( - C9841A1E2CA309310062A834 /* MockFeatureConfigRepositoryProtocol.swift */, - ); - path = Mock; - sourceTree = ""; - }; - C98433E52CCA867F009723D4 /* Message */ = { - isa = PBXGroup; - children = ( - C98433ED2CCB7EDF009723D4 /* Models */, - C98433E62CCA868F009723D4 /* MessageRepository.swift */, - C98433E82CCA86CA009723D4 /* MessageLocalStore.swift */, - ); - path = Message; - sourceTree = ""; - }; - C98433ED2CCB7EDF009723D4 /* Models */ = { - isa = PBXGroup; - children = ( - C98433EE2CCB7EE6009723D4 /* MessageType.swift */, - ); - path = Models; - sourceTree = ""; - }; - C98433F32CCBC26E009723D4 /* LocalStores */ = { - isa = PBXGroup; - children = ( - C98433F42CCBC290009723D4 /* MessageLocalStoreTests.swift */, - ); - path = LocalStores; - sourceTree = ""; - }; - C99322B42C986E3A0065E10F /* Team */ = { - isa = PBXGroup; - children = ( - C99322B22C986E3A0065E10F /* TeamRepository.swift */, - C99322B32C986E3A0065E10F /* TeamRepositoryError.swift */, - ); - path = Team; - sourceTree = ""; - }; - C99322B92C986E3A0065E10F /* User */ = { - isa = PBXGroup; - children = ( - C99322B52C986E3A0065E10F /* SelfUserProvider.swift */, - C99322B62C986E3A0065E10F /* UserModelMappings.swift */, - C99322B72C986E3A0065E10F /* UserRepository.swift */, - C97C01D62CC13E1A000683C5 /* UserLocalStore.swift */, - C99322B82C986E3A0065E10F /* UserRepositoryError.swift */, - ); - path = User; - sourceTree = ""; - }; - C99322BC2C986E3A0065E10F /* UpdateEvents */ = { - isa = PBXGroup; - children = ( - C99322BA2C986E3A0065E10F /* UpdateEventsRepository.swift */, - C99322BB2C986E3A0065E10F /* UpdateEventsRepositoryError.swift */, - ); - path = UpdateEvents; - sourceTree = ""; - }; - C99322C22C986E3A0065E10F /* FeatureConfig */ = { - isa = PBXGroup; - children = ( - C99322BF2C986E3A0065E10F /* FeatureConfigModelMappings.swift */, - C99322C02C986E3A0065E10F /* FeatureConfigRepository.swift */, - C99322C12C986E3A0065E10F /* FeatureConfigRepositoryError.swift */, - ); - path = FeatureConfig; - sourceTree = ""; - }; - C99322CB2C986E3A0065E10F /* Conversations */ = { - isa = PBXGroup; - children = ( - C96B75472CDBA10F003A85EB /* Models */, - C99322C32C986E3A0065E10F /* ConversationLocalStore.swift */, - C99322C42C986E3A0065E10F /* ConversationLocalStore+Group.swift */, - C99322C52C986E3A0065E10F /* ConversationLocalStore+Metadata.swift */, - C99322C62C986E3A0065E10F /* ConversationLocalStore+MLS.swift */, - C99322C72C986E3A0065E10F /* ConversationLocalStore+Status.swift */, - C99322C82C986E3A0065E10F /* ConversationModelMappings.swift */, - C99322C92C986E3A0065E10F /* ConversationRepository.swift */, - C99322CA2C986E3A0065E10F /* ConversationRepositoryError.swift */, - ); - path = Conversations; - sourceTree = ""; - }; - C99322CE2C986E3A0065E10F /* Connections */ = { - isa = PBXGroup; - children = ( - C9FDF3EA2CAA988100D78098 /* ConnectionsModelMappings.swift */, - C91F111B2C9C4B2B00BA5BE2 /* ConnectionsLocalStore.swift */, - C91F111A2C9C4B2B00BA5BE2 /* ConnectionsRepository.swift */, - C99322CD2C986E3A0065E10F /* ConnectionsRepositoryError.swift */, - ); - path = Connections; - sourceTree = ""; - }; - C99322D12C986E3A0065E10F /* ConversationsLabels */ = { - isa = PBXGroup; - children = ( - C99322CF2C986E3A0065E10F /* ConversationLabelsRepository.swift */, - C99322D02C986E3A0065E10F /* ConversationLabelsRepositoryError.swift */, - ); - path = ConversationsLabels; - sourceTree = ""; - }; - C9C8FDC42C9DBE0E00702B91 /* FeatureConfigEventProcessor */ = { - isa = PBXGroup; - children = ( - C9C8FDC32C9DBE0E00702B91 /* FeatureConfigUpdateEventProcessorTests.swift */, - ); - path = FeatureConfigEventProcessor; - sourceTree = ""; - }; - C9C8FDC82C9DBE0E00702B91 /* TeamEventProcessor */ = { - isa = PBXGroup; - children = ( - C9C8FDC52C9DBE0E00702B91 /* TeamDeleteEventProcessorTests.swift */, - C9C8FDC62C9DBE0E00702B91 /* TeamMemberLeaveEventProcessorTests.swift */, - C9C8FDC72C9DBE0E00702B91 /* TeamMemberUpdateEventProcessorTests.swift */, - ); - path = TeamEventProcessor; - sourceTree = ""; - }; - C9C8FDCC2C9DBE0E00702B91 /* UserEventProcessor */ = { - isa = PBXGroup; - children = ( - C97C01BA2CBE5E65000683C5 /* UserUpdateEventProcessorTests.swift */, - C97C014F2CB01BDF000683C5 /* OneOnOneResolverTests.swift */, - C97C01512CB01BEF000683C5 /* UserConnectionEventProcessorTests.swift */, - C97C015B2CB40038000683C5 /* UserPropertiesSetEventProcessorTests.swift */, - C97C013C2CAD7D69000683C5 /* UserPropertiesDeleteEventProcessorTests.swift */, - C9F691282C9B164A008CC41F /* UserPushRemoveEventProcessorTests.swift */, - C9C8FDC92C9DBE0E00702B91 /* UserClientAddEventProcessorTests.swift */, - C9C8FDCA2C9DBE0E00702B91 /* UserLegalHoldDisableEventProcessorTests.swift */, - C9C8FDCB2C9DBE0E00702B91 /* UserLegalholdRequestEventProcessorTests.swift */, - C97C01AB2CB92D47000683C5 /* UserLegalholdEnableEventProcessorTests.swift */, - C97C01532CB04626000683C5 /* UserDeleteEventProcessorTests.swift */, - ); - path = UserEventProcessor; - sourceTree = ""; - }; - C9C8FDCD2C9DBE0E00702B91 /* Event Processing */ = { - isa = PBXGroup; - children = ( - C96B75282CDB7688003A85EB /* ConversationEventProcessor */, - C97C01582CB40010000683C5 /* FederationEventProcessor */, - C9C8FDC42C9DBE0E00702B91 /* FeatureConfigEventProcessor */, - C9C8FDC82C9DBE0E00702B91 /* TeamEventProcessor */, - C9C8FDCC2C9DBE0E00702B91 /* UserEventProcessor */, - ); - path = "Event Processing"; - sourceTree = ""; - }; - C9EA76AD2C98548900A7D35C /* UseCases */ = { - isa = PBXGroup; - children = ( - C9EA769E2C92DD0F00A7D35C /* PushSupportedProtocolsUseCase.swift */, - ); - path = UseCases; - sourceTree = ""; - }; - EE0E117C2C2C4076004BBD29 /* Helpers */ = { - isa = PBXGroup; - children = ( - EE0E117D2C2C4080004BBD29 /* TestError.swift */, - CB7979152C738547006FBA58 /* TestSetup.swift */, - ); - path = Helpers; - sourceTree = ""; - }; - EE368CCD2C2DAA87009DBAB0 /* Event Processing */ = { - isa = PBXGroup; - children = ( - EE368CCB2C2DAA87009DBAB0 /* UpdateEventProcessor.swift */, - EEAD09F02C46602300CC8658 /* ConversationEventProcessor */, - EEAD0A342C46BB7500CC8658 /* FeatureConfigEventProcessor */, - EEAD0A2F2C46B97700CC8658 /* FederationEventProcessor */, - EEAD0A0F2C46A32A00CC8658 /* TeamEventProcessor */, - EEAD0A162C46A87100CC8658 /* UserEventProcessor */, - ); - path = "Event Processing"; - sourceTree = ""; - }; - EE57A6FC2C298F280096F242 /* Event Decryption */ = { - isa = PBXGroup; - children = ( - EE57A6FD2C298F380096F242 /* UpdateEventDecryptor.swift */, - EE57A6FF2C298F630096F242 /* ProteusMessageDecryptor.swift */, - EE57A7062C2A86880096F242 /* ProteusMessageDecryptorError.swift */, - ); - path = "Event Decryption"; - sourceTree = ""; - }; - EE57A7092C2A8B950096F242 /* Event Decryption */ = { - isa = PBXGroup; - children = ( - EE57A70A2C2A8BAA0096F242 /* UpdateEventDecryptorTests.swift */, - EE3F97532C2ADC4C00668DF1 /* ProteusMessageDecryptorTests.swift */, - ); - path = "Event Decryption"; - sourceTree = ""; - }; - EEAD09F02C46602300CC8658 /* ConversationEventProcessor */ = { - isa = PBXGroup; - children = ( - EE368CC72C2DAA87009DBAB0 /* ConversationEventProcessor.swift */, - EEAD09F12C46604400CC8658 /* ConversationAccessUpdateEventProcessor.swift */, - EEAD09F32C46709800CC8658 /* ConversationCodeUpdateEventProcessor.swift */, - EEAD09F52C46772000CC8658 /* ConversationCreateEventProcessor.swift */, - EEAD09F72C46772C00CC8658 /* ConversationDeleteEventProcessor.swift */, - EEAD09F92C46773300CC8658 /* ConversationMemberJoinEventProcessor.swift */, - EEAD09FB2C46773900CC8658 /* ConversationMemberLeaveEventProcessor.swift */, - EEAD09FD2C46774200CC8658 /* ConversationMemberUpdateEventProcessor.swift */, - EEAD09FF2C46774900CC8658 /* ConversationMessageTimerUpdateEventProcessor.swift */, - EEAD0A012C46774F00CC8658 /* ConversationMLSMessageAddEventProcessor.swift */, - EEAD0A032C46775700CC8658 /* ConversationMLSWelcomeEventProcessor.swift */, - EEAD0A052C46775D00CC8658 /* ConversationProteusMessageAddEventProcessor.swift */, - EEAD0A072C46776400CC8658 /* ConversationProtocolUpdateEventProcessor.swift */, - EEAD0A092C46776B00CC8658 /* ConversationReceiptModeUpdateEventProcessor.swift */, - EEAD0A0B2C46777200CC8658 /* ConversationRenameEventProcessor.swift */, - EEAD0A0D2C46777800CC8658 /* ConversationTypingEventProcessor.swift */, - ); - path = ConversationEventProcessor; - sourceTree = ""; - }; - EEAD0A0F2C46A32A00CC8658 /* TeamEventProcessor */ = { - isa = PBXGroup; - children = ( - EE368CCA2C2DAA87009DBAB0 /* TeamEventProcessor.swift */, - EEAD0A142C46A3AB00CC8658 /* TeamDeleteEventProcessor.swift */, - EEAD0A122C46A38200CC8658 /* TeamMemberLeaveEventProcessor.swift */, - EEAD0A102C46A33E00CC8658 /* TeamMemberUpdateEventProcessor.swift */, - ); - path = TeamEventProcessor; - sourceTree = ""; - }; - EEAD0A162C46A87100CC8658 /* UserEventProcessor */ = { - isa = PBXGroup; - children = ( - C97C014A2CB00F92000683C5 /* OneOnOneResolver.swift */, - EE368CCC2C2DAA87009DBAB0 /* UserEventProcessor.swift */, - EEAD0A172C46A88D00CC8658 /* UserClientAddEventProcessor.swift */, - EEAD0A192C46A92000CC8658 /* UserClientRemoveEventProcessor.swift */, - EEAD0A1B2C46A99D00CC8658 /* UserConnectionEventProcessor.swift */, - EEAD0A1F2C46AB8700CC8658 /* UserDeleteEventProcessor.swift */, - EEAD0A212C46ABFA00CC8658 /* UserLegalholdDisableEventProcessor.swift */, - EEAD0A232C46ACEA00CC8658 /* UserLegalholdEnableEventProcessor.swift */, - EEAD0A252C46AD4D00CC8658 /* UserLegalholdRequestEventProcessor.swift */, - EEAD0A272C46AE6400CC8658 /* UserPropertiesSetEventProcessor.swift */, - EEAD0A292C46AEB600CC8658 /* UserPropertiesDeleteEventProcessor.swift */, - EEAD0A2B2C46AF9200CC8658 /* UserPushRemoveEventProcessor.swift */, - EEAD0A2D2C46B01900CC8658 /* UserUpdateEventProcessor.swift */, - ); - path = UserEventProcessor; - sourceTree = ""; - }; - EEAD0A2F2C46B97700CC8658 /* FederationEventProcessor */ = { - isa = PBXGroup; - children = ( - EE368CC92C2DAA87009DBAB0 /* FederationEventProcessor.swift */, - EEAD0A302C46B98D00CC8658 /* FederationConnectionRemovedEventProcessor.swift */, - EEAD0A322C46B99800CC8658 /* FederationDeleteEventProcessor.swift */, - ); - path = FederationEventProcessor; - sourceTree = ""; - }; - EEAD0A342C46BB7500CC8658 /* FeatureConfigEventProcessor */ = { - isa = PBXGroup; - children = ( - EE368CC82C2DAA87009DBAB0 /* FeatureConfigEventProcessor.swift */, - EEAD0A352C46BBA600CC8658 /* FeatureConfigUpdateEventProcessor.swift */, - ); - path = FeatureConfigEventProcessor; - sourceTree = ""; - }; - EEC410252C60D48900E89394 /* Synchronization */ = { - isa = PBXGroup; - children = ( - EEC410242C60D48900E89394 /* SyncManagerTests.swift */, - ); - path = Synchronization; - sourceTree = ""; - }; - EECC35A42C2EB6C100679448 /* Synchronization */ = { - isa = PBXGroup; - children = ( - EECC35A52C2EB6CD00679448 /* SyncManager.swift */, - EECC35A72C2EB70400679448 /* SyncState.swift */, - C98433CF2CC26A1D009723D4 /* MLSProvider.swift */, - ); - path = Synchronization; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -889,10 +218,16 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 59EA7A282D00CFB2002CA0B8 /* WireDomain */, + ); name = WireDomain; packageProductDependencies = ( 59909A5D2C5BBEA8009C41DE /* WireAPI */, 598D042C2C89C63100B64D71 /* WireFoundation */, + 59EA774E2D00CE0C002CA0B8 /* WireLogging */, + 766BE3C42D036D5000148CB6 /* WireDomainAPI */, + 594904942D0710BF00238104 /* WireAnalytics */, ); productName = WireDomain; productReference = 01D0DCA62C1C8C870076CB1C /* WireDomain.framework */; @@ -911,6 +246,10 @@ dependencies = ( 01D0DCB32C1C8C880076CB1C /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 59EA76E82D00CD88002CA0B8 /* TestPlans */, + 59EA78992D00CF1C002CA0B8 /* WireDomainTests */, + ); name = WireDomainTests; packageProductDependencies = ( 59909A6C2C5BC08B009C41DE /* WireAPISupport */, @@ -1014,7 +353,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 017F67AF2C20803300B6E02D /* AutoMockable.generated.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1022,85 +360,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C99322D22C986E3A0065E10F /* TeamRepository.swift in Sources */, - EEAD0A042C46775700CC8658 /* ConversationMLSWelcomeEventProcessor.swift in Sources */, - C99322DD2C986E3A0065E10F /* FeatureConfigRepositoryError.swift in Sources */, - C99322E72C986E3A0065E10F /* ConnectionsRepositoryError.swift in Sources */, - C98433D02CC26A1D009723D4 /* MLSProvider.swift in Sources */, - EEAD0A362C46BBA600CC8658 /* FeatureConfigUpdateEventProcessor.swift in Sources */, - EE57A7002C298F630096F242 /* ProteusMessageDecryptor.swift in Sources */, - EE368CD02C2DAA87009DBAB0 /* FederationEventProcessor.swift in Sources */, - EEAD09FA2C46773300CC8658 /* ConversationMemberJoinEventProcessor.swift in Sources */, - C99322E12C986E3A0065E10F /* ConversationLocalStore+MLS.swift in Sources */, - C97C014B2CB00F92000683C5 /* OneOnOneResolver.swift in Sources */, - C99322D52C986E3A0065E10F /* UserModelMappings.swift in Sources */, - C91F111D2C9C4B2B00BA5BE2 /* ConnectionsLocalStore.swift in Sources */, - EEAD0A022C46774F00CC8658 /* ConversationMLSMessageAddEventProcessor.swift in Sources */, - EE368CCE2C2DAA87009DBAB0 /* ConversationEventProcessor.swift in Sources */, - EEAD0A2C2C46AF9200CC8658 /* UserPushRemoveEventProcessor.swift in Sources */, 0163EE812C20D71C00B37260 /* WireDomain.docc in Sources */, - EEAD0A2E2C46B01900CC8658 /* UserUpdateEventProcessor.swift in Sources */, - EEAD0A2A2C46AEB600CC8658 /* UserPropertiesDeleteEventProcessor.swift in Sources */, - C9FDF3EC2CAA988900D78098 /* ConnectionsModelMappings.swift in Sources */, - C99322D42C986E3A0065E10F /* SelfUserProvider.swift in Sources */, - C97C01D72CC13E1A000683C5 /* UserLocalStore.swift in Sources */, - EEAD0A332C46B99800CC8658 /* FederationDeleteEventProcessor.swift in Sources */, - C99322E42C986E3A0065E10F /* ConversationRepository.swift in Sources */, - C99322E02C986E3A0065E10F /* ConversationLocalStore+Metadata.swift in Sources */, - EEAD0A062C46775D00CC8658 /* ConversationProteusMessageAddEventProcessor.swift in Sources */, - C91F111C2C9C4B2B00BA5BE2 /* ConnectionsRepository.swift in Sources */, - EEAD0A282C46AE6400CC8658 /* UserPropertiesSetEventProcessor.swift in Sources */, - C96B75432CDB8E03003A85EB /* NSManagedObjectContext.swift in Sources */, - C99322E32C986E3A0065E10F /* ConversationModelMappings.swift in Sources */, - EEAD09FC2C46773900CC8658 /* ConversationMemberLeaveEventProcessor.swift in Sources */, - EE57A7082C2A8B740096F242 /* ProteusMessageDecryptorError.swift in Sources */, - EEAD0A1A2C46A92000CC8658 /* UserClientRemoveEventProcessor.swift in Sources */, - C98433E92CCA86CA009723D4 /* MessageLocalStore.swift in Sources */, - EEAD0A0A2C46776B00CC8658 /* ConversationReceiptModeUpdateEventProcessor.swift in Sources */, - C99322D62C986E3A0065E10F /* UserRepository.swift in Sources */, - C98433EF2CCB7EE6009723D4 /* MessageType.swift in Sources */, - EEAD0A002C46774900CC8658 /* ConversationMessageTimerUpdateEventProcessor.swift in Sources */, - EEAD0A182C46A88D00CC8658 /* UserClientAddEventProcessor.swift in Sources */, - C99322DC2C986E3A0065E10F /* FeatureConfigRepository.swift in Sources */, - C99322E22C986E3A0065E10F /* ConversationLocalStore+Status.swift in Sources */, - EEAD0A0E2C46777800CC8658 /* ConversationTypingEventProcessor.swift in Sources */, - C99322D92C986E3A0065E10F /* UpdateEventsRepositoryError.swift in Sources */, - EEAD0A202C46AB8700CC8658 /* UserDeleteEventProcessor.swift in Sources */, - EEAD0A152C46A3AB00CC8658 /* TeamDeleteEventProcessor.swift in Sources */, - EEAD0A312C46B98D00CC8658 /* FederationConnectionRemovedEventProcessor.swift in Sources */, - EEAD09F62C46772000CC8658 /* ConversationCreateEventProcessor.swift in Sources */, - EEAD0A1C2C46A99D00CC8658 /* UserConnectionEventProcessor.swift in Sources */, - EEAD09F82C46772C00CC8658 /* ConversationDeleteEventProcessor.swift in Sources */, - EEAD0A112C46A33E00CC8658 /* TeamMemberUpdateEventProcessor.swift in Sources */, - C99322E82C986E3A0065E10F /* ConversationLabelsRepository.swift in Sources */, - C99322E52C986E3A0065E10F /* ConversationRepositoryError.swift in Sources */, - C98433E72CCA868F009723D4 /* MessageRepository.swift in Sources */, - C99322DF2C986E3A0065E10F /* ConversationLocalStore+Group.swift in Sources */, - C99322DE2C986E3A0065E10F /* ConversationLocalStore.swift in Sources */, - EEAD0A242C46ACEA00CC8658 /* UserLegalholdEnableEventProcessor.swift in Sources */, - C99322D72C986E3A0065E10F /* UserRepositoryError.swift in Sources */, - EEAD0A132C46A38200CC8658 /* TeamMemberLeaveEventProcessor.swift in Sources */, - EEAD0A262C46AD4D00CC8658 /* UserLegalholdRequestEventProcessor.swift in Sources */, - EEAD09FE2C46774200CC8658 /* ConversationMemberUpdateEventProcessor.swift in Sources */, - EEAD09F42C46709800CC8658 /* ConversationCodeUpdateEventProcessor.swift in Sources */, - EE368CCF2C2DAA87009DBAB0 /* FeatureConfigEventProcessor.swift in Sources */, - EECC35A82C2EB70400679448 /* SyncState.swift in Sources */, - C99322D82C986E3A0065E10F /* UpdateEventsRepository.swift in Sources */, - EE368CD12C2DAA87009DBAB0 /* TeamEventProcessor.swift in Sources */, - C99322D32C986E3A0065E10F /* TeamRepositoryError.swift in Sources */, - EEAD0A222C46ABFB00CC8658 /* UserLegalholdDisableEventProcessor.swift in Sources */, - EEAD09F22C46604400CC8658 /* ConversationAccessUpdateEventProcessor.swift in Sources */, - EEAD0A082C46776400CC8658 /* ConversationProtocolUpdateEventProcessor.swift in Sources */, - C9EA769F2C92DD0F00A7D35C /* PushSupportedProtocolsUseCase.swift in Sources */, - EE368CD32C2DAA87009DBAB0 /* UserEventProcessor.swift in Sources */, - C96B75482CDBA10F003A85EB /* SystemMessage.swift in Sources */, - EE57A6FE2C298F380096F242 /* UpdateEventDecryptor.swift in Sources */, - C99322E92C986E3A0065E10F /* ConversationLabelsRepositoryError.swift in Sources */, - EEAD0A0C2C46777200CC8658 /* ConversationRenameEventProcessor.swift in Sources */, - C99322DB2C986E3A0065E10F /* FeatureConfigModelMappings.swift in Sources */, - C97C01A52CB80F54000683C5 /* UserClientsRepository.swift in Sources */, - EE368CD22C2DAA87009DBAB0 /* UpdateEventProcessor.swift in Sources */, - EECC35A62C2EB6CD00679448 /* SyncManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1108,48 +368,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C9F691292C9B164A008CC41F /* UserPushRemoveEventProcessorTests.swift in Sources */, - C93961932C91B15B00EA971A /* ConversationRepositoryTests.swift in Sources */, - C96B75292CDB7688003A85EB /* ConversationMemberUpdateEventProcessorTests.swift in Sources */, - C96B752A2CDB7688003A85EB /* ConversationMemberJoinEventTests.swift in Sources */, - C96B752B2CDB7688003A85EB /* ConversationProtocolUpdateEventProcessorTests.swift in Sources */, - C96B752C2CDB7688003A85EB /* ConversationAccessUpdateEventProcessorTests.swift in Sources */, - C96B752D2CDB7688003A85EB /* ConversationCreateEventProcessorTests.swift in Sources */, - C96B752E2CDB7688003A85EB /* ConversationReceiptModeUpdateEventProcessorTests.swift in Sources */, - C96B752F2CDB7688003A85EB /* ConversationMemberLeaveEventTests.swift in Sources */, - C93961922C91B12800EA971A /* TestError.swift in Sources */, - EEC410262C60D48900E89394 /* SyncManagerTests.swift in Sources */, - C9C8FDD32C9DBE0E00702B91 /* UserLegalHoldDisableEventProcessorTests.swift in Sources */, - EE57A7032C2994420096F242 /* UpdateEventsRepositoryTests.swift in Sources */, - C97C01522CB01BEF000683C5 /* UserConnectionEventProcessorTests.swift in Sources */, - C98433F52CCBC290009723D4 /* MessageLocalStoreTests.swift in Sources */, - C98433F12CCBC251009723D4 /* MessageRepositoryTests.swift in Sources */, - C9C8FDD02C9DBE0E00702B91 /* TeamMemberLeaveEventProcessorTests.swift in Sources */, - C97C01A82CB813C9000683C5 /* UserClientsRepositoryTests.swift in Sources */, - C9E8A3C02C761EDD0093DD5C /* FeatureConfigRepositoryTests.swift in Sources */, - C9841A1F2CA309310062A834 /* MockFeatureConfigRepositoryProtocol.swift in Sources */, - C97C015C2CB40038000683C5 /* UserPropertiesSetEventProcessorTests.swift in Sources */, - CB7979162C738547006FBA58 /* TestSetup.swift in Sources */, - 162356502C2B223100C6666C /* UserRepositoryTests.swift in Sources */, - C96B75452CDBA0A9003A85EB /* ConversationDeleteEventProcessorTests.swift in Sources */, - EE3F97542C2ADC4C00668DF1 /* ProteusMessageDecryptorTests.swift in Sources */, - EE57A70B2C2A8BAA0096F242 /* UpdateEventDecryptorTests.swift in Sources */, - C9E8A3AE2C73878B0093DD5C /* ConnectionsRepositoryTests.swift in Sources */, - C97C015F2CB40EF3000683C5 /* PushSupportedProtocolsUseCaseTests.swift in Sources */, - C97C01BB2CBE5E65000683C5 /* UserUpdateEventProcessorTests.swift in Sources */, - C97C013D2CAD7D69000683C5 /* UserPropertiesDeleteEventProcessorTests.swift in Sources */, - C97C01502CB01BDF000683C5 /* OneOnOneResolverTests.swift in Sources */, - C9C8FDD22C9DBE0E00702B91 /* UserClientAddEventProcessorTests.swift in Sources */, - C9C8FDD12C9DBE0E00702B91 /* TeamMemberUpdateEventProcessorTests.swift in Sources */, - C97C01542CB04626000683C5 /* UserDeleteEventProcessorTests.swift in Sources */, - C9C8FDCE2C9DBE0E00702B91 /* FeatureConfigUpdateEventProcessorTests.swift in Sources */, - C9C8FDCF2C9DBE0E00702B91 /* TeamDeleteEventProcessorTests.swift in Sources */, - C97C01592CB40010000683C5 /* FederationConnectionRemovedEventProcessorTests.swift in Sources */, - C97C01AC2CB92D47000683C5 /* UserLegalholdEnableEventProcessorTests.swift in Sources */, - C97C015A2CB40010000683C5 /* FederationDeleteEventProcessorTests.swift in Sources */, - C9C8FDD42C9DBE0E00702B91 /* UserLegalholdRequestEventProcessorTests.swift in Sources */, - 017F679C2C20801800B6E02D /* TeamRepositoryTests.swift in Sources */, - C9E8A3B72C749F2A0093DD5C /* ConversationLabelsRepositoryTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1532,6 +750,10 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ + 594904942D0710BF00238104 /* WireAnalytics */ = { + isa = XCSwiftPackageProductDependency; + productName = WireAnalytics; + }; 598D042C2C89C63100B64D71 /* WireFoundation */ = { isa = XCSwiftPackageProductDependency; productName = WireFoundation; @@ -1548,6 +770,14 @@ isa = XCSwiftPackageProductDependency; productName = WireAPISupport; }; + 59EA774E2D00CE0C002CA0B8 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; + 766BE3C42D036D5000148CB6 /* WireDomainAPI */ = { + isa = XCSwiftPackageProductDependency; + productName = WireDomainAPI; + }; C9E0C9BA2C91B76F00CE6607 /* WireTestingPackage */ = { isa = XCSwiftPackageProductDependency; productName = WireTestingPackage; diff --git a/WireDomain/Project/WireDomain Project.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/WireDomain/Project/WireDomain Project.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000000..08de0be8d3c --- /dev/null +++ b/WireDomain/Project/WireDomain Project.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + diff --git a/WireDomain/Project/WireDomain Project.xcodeproj/xcshareddata/xcschemes/WireDomain.xcscheme b/WireDomain/Project/WireDomain Project.xcodeproj/xcshareddata/xcschemes/WireDomain.xcscheme index 6a736cf7971..aed6939ca90 100644 --- a/WireDomain/Project/WireDomain Project.xcodeproj/xcshareddata/xcschemes/WireDomain.xcscheme +++ b/WireDomain/Project/WireDomain Project.xcodeproj/xcshareddata/xcschemes/WireDomain.xcscheme @@ -1,6 +1,6 @@ + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WireDomain/Sources/PackageSupport/Sourcery/sourcery.yml b/WireDomain/Sources/PackageSupport/Sourcery/sourcery.yml index af8b305c908..11346382dff 100644 --- a/WireDomain/Sources/PackageSupport/Sourcery/sourcery.yml +++ b/WireDomain/Sources/PackageSupport/Sourcery/sourcery.yml @@ -1,8 +1,8 @@ sources: -- ${PACKAGE_ROOT_DIR}/Sources/WireDomain +- ${PACKAGE_ROOT_DIR}/Sources/Package templates: - ${TARGET_DIR}/Sourcery/AutoMockable.stencil output: - ${DERIVED_SOURCES_DIR} + ${DERIVED_SOURCES_DIR} args: - autoMockableImports: ["WireDomainPkg"] + autoMockableImports: ["WireDomainPkg"] diff --git a/WireDomain/Sources/WireDomain/Event Decryption/ProteusMessageDecryptor.swift b/WireDomain/Sources/WireDomain/Event Decryption/ProteusMessageDecryptor.swift index 0aa46b3bd6c..3dd9b19c7cb 100644 --- a/WireDomain/Sources/WireDomain/Event Decryption/ProteusMessageDecryptor.swift +++ b/WireDomain/Sources/WireDomain/Event Decryption/ProteusMessageDecryptor.swift @@ -40,8 +40,6 @@ struct ProteusMessageDecryptor: ProteusMessageDecryptorProtocol { let proteusService: any ProteusServiceInterface let managedObjectContext: NSManagedObjectContext - private let maxCiphertextSize = Int(12_000 * 1.5) - typealias Context = ( selfClient: WireDataModel.UserClient, senderUser: WireDataModel.ZMUser, @@ -49,14 +47,6 @@ struct ProteusMessageDecryptor: ProteusMessageDecryptorProtocol { proteusSessionID: ProteusSessionID ) - init( - proteusService: any ProteusServiceInterface, - managedObjectContext: NSManagedObjectContext - ) { - self.proteusService = proteusService - self.managedObjectContext = managedObjectContext - } - func decryptedEventData( from eventData: ConversationProteusMessageAddEvent ) async throws -> ConversationProteusMessageAddEvent { @@ -66,11 +56,6 @@ struct ProteusMessageDecryptor: ProteusMessageDecryptorProtocol { } let ciphertextData = try validateCiphertext(ciphertext) - - if case let .ciphertext(externalCiphertext) = eventData.externalData { - try validateExternalCiphertext(externalCiphertext) - } - let context = try await extractContext(from: eventData) let (didCreateSession, plaintextData) = try await proteusService.decrypt( @@ -95,25 +80,13 @@ struct ProteusMessageDecryptor: ProteusMessageDecryptorProtocol { throw ProteusMessageDecryptorError.senderFailedToEncrypt } - guard - ciphertext.count <= maxCiphertextSize, - let ciphertextData = Data(base64Encoded: ciphertext) - else { - throw ProteusError.decodeError + guard let ciphertextData = Data(base64Encoded: ciphertext) else { + throw ProteusMessageDecryptorError.invalidCiphertext } return ciphertextData } - private func validateExternalCiphertext(_ ciphertext: String) throws { - // External messages aren't encrypted via Proteus, instead they are symmetrically - // encrypted with a key that is E2EE via Proteus. Decryption of external messages - // happens during event processing, here we just want to validate it. - guard ciphertext.count <= maxCiphertextSize else { - throw ProteusError.decodeError - } - } - private func extractContext( from eventData: ConversationProteusMessageAddEvent ) async throws -> Context { diff --git a/WireDomain/Sources/WireDomain/Event Decryption/ProteusMessageDecryptorError.swift b/WireDomain/Sources/WireDomain/Event Decryption/ProteusMessageDecryptorError.swift index 7b5a64dfb23..81b61572845 100644 --- a/WireDomain/Sources/WireDomain/Event Decryption/ProteusMessageDecryptorError.swift +++ b/WireDomain/Sources/WireDomain/Event Decryption/ProteusMessageDecryptorError.swift @@ -22,5 +22,6 @@ enum ProteusMessageDecryptorError: Error { case senderClientNotFound case proteusSessionIDNotFound case senderFailedToEncrypt + case invalidCiphertext } diff --git a/WireDomain/Sources/WireDomain/Event Decryption/UpdateEventDecryptor.swift b/WireDomain/Sources/WireDomain/Event Decryption/UpdateEventDecryptor.swift index 42a3d8a0a6d..c31935be635 100644 --- a/WireDomain/Sources/WireDomain/Event Decryption/UpdateEventDecryptor.swift +++ b/WireDomain/Sources/WireDomain/Event Decryption/UpdateEventDecryptor.swift @@ -18,7 +18,9 @@ import Foundation import WireAPI +import WireCoreCrypto import WireDataModel +import WireLogging // sourcery: AutoMockable /// Decrypt the E2EE content within update events. @@ -76,8 +78,7 @@ struct UpdateEventDecryptor: UpdateEventDecryptorProtocol { do { let decryptedEventData = try await proteusMessageDecryptor.decryptedEventData(from: eventData) decryptedEvents.append(.conversation(.proteusMessageAdd(decryptedEventData))) - - } catch let error as ProteusError { + } catch let error as ProteusService.DecryptionError { WireLogger.updateEvent.error( "failed to decrypt proteus event payload, dropping: \(error.localizedDescription)", attributes: logAttributes @@ -85,7 +86,7 @@ struct UpdateEventDecryptor: UpdateEventDecryptorProtocol { await appendFailedToDecryptProteusMessage( eventData: eventData, - error: error + error: error.proteusError ) } catch { WireLogger.updateEvent.error( @@ -108,7 +109,7 @@ struct UpdateEventDecryptor: UpdateEventDecryptorProtocol { error: ProteusError ) async { // Do not notify the user if the error is just "duplicated". - if error == .outdatedMessage || error == .duplicateMessage { + if error == .DuplicateMessage { return } @@ -135,7 +136,7 @@ struct UpdateEventDecryptor: UpdateEventDecryptorProtocol { at: eventData.timestamp, sender: sender, client: senderClient, - errorCode: error.rawValue + error: error ) } } diff --git a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationCreateEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationCreateEventProcessor.swift index 67b4137ce41..3e1ff6af9ff 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationCreateEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationCreateEventProcessor.swift @@ -17,6 +17,7 @@ // import WireAPI +import WireLogging import WireSystem /// Process conversation create events. @@ -51,7 +52,7 @@ struct ConversationCreateEventProcessor: ConversationCreateEventProcessorProtoco } await repository.storeConversation( - conversation, + conversation.toDomainModel(), timestamp: timestamp ) } diff --git a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationEventProcessor.swift index cf0dd124f34..b2d08dd25c7 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationEventProcessor.swift @@ -36,7 +36,6 @@ protocol ConversationEventProcessorProtocol { struct ConversationEventProcessor { let accessUpdateEventProcessor: any ConversationAccessUpdateEventProcessorProtocol - let codeUpdateEventProcessor: any ConversationCodeUpdateEventProcessorProtocol let createEventProcessor: any ConversationCreateEventProcessorProtocol let deleteEventProcessor: any ConversationDeleteEventProcessorProtocol let memberJoinEventProcessor: any ConversationMemberJoinEventProcessorProtocol @@ -54,13 +53,15 @@ struct ConversationEventProcessor { func processEvent(_ event: ConversationEvent) async throws { switch event { case let .accessUpdate(event): - try await accessUpdateEventProcessor.processEvent(event) + await accessUpdateEventProcessor.processEvent(event) - case let .codeUpdate(event): - try await codeUpdateEventProcessor.processEvent(event) + case .codeUpdate: + // Event is not currently processed instead we fetch guest link on demand directly from API, see + // `CreateConversationGuestLinkUseCase` and `CreateConversationGuestLinkActionHandler` + break case let .create(event): - try await createEventProcessor.processEvent(event) + await createEventProcessor.processEvent(event) case let .delete(event): try await deleteEventProcessor.processEvent(event) @@ -75,7 +76,7 @@ struct ConversationEventProcessor { try await memberUpdateEventProcessor.processEvent(event) case let .messageTimerUpdate(event): - try await messageTimerUpdateEventProcessor.processEvent(event) + await messageTimerUpdateEventProcessor.processEvent(event) case let .mlsMessageAdd(event): try await mlsMessageAddEventProcessor.processEvent(event) diff --git a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationMLSWelcomeEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationMLSWelcomeEventProcessor.swift index 78ed4a18d38..4d36f874de4 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationMLSWelcomeEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationMLSWelcomeEventProcessor.swift @@ -17,6 +17,8 @@ // import WireAPI +import WireDataModel +import WireLogging /// Process conversation mls welcome events. @@ -32,9 +34,77 @@ protocol ConversationMLSWelcomeEventProcessorProtocol { struct ConversationMLSWelcomeEventProcessor: ConversationMLSWelcomeEventProcessorProtocol { - func processEvent(_: ConversationMLSWelcomeEvent) async throws { - // TODO: [WPB-10173] - assertionFailure("not implemented yet") + enum Failure: Error { + case conversationNotFound } + let conversationRepository: any ConversationRepositoryProtocol + let conversationLocalStore: any ConversationLocalStoreProtocol + let mlsService: any MLSServiceInterface + let mlsDecryptionService: any MLSDecryptionServiceInterface + let oneOnOneResolver: any OneOnOneResolverProtocol + + func processEvent(_ event: ConversationMLSWelcomeEvent) async throws { + let welcomeMessage = event.welcomeMessage + let conversationID = event.conversationID + + WireLogger.mls.info("MLS event processor is processing welcome message") + + // Decrypts the welcome message which returns the group ID of the conversation we were added to. + let groupID = try await mlsDecryptionService.processWelcomeMessage( + welcomeMessage: welcomeMessage + ) + + var conversation = await conversationRepository.fetchConversation( + id: conversationID.uuid, + domain: conversationID.domain + ) + + if conversation == nil { + // sync conversation with backend + try await conversationRepository.pullConversation( + id: conversationID.uuid, + domain: conversationID.domain + ) + + conversation = await conversationRepository.fetchConversation( + id: conversationID.uuid, + domain: conversationID.domain + ) + } + + guard let conversation else { + throw Failure.conversationNotFound + } + + // This conversation is now a MLS one so we need to update its group ID and set MLS status to ready.. + await conversationLocalStore.storeMLSConversationEstablished( + mlsGroupID: groupID, + conversation: conversation + ) + + // ..and also update/create the related MLS group. + await conversationLocalStore.updateOrCreateMLSGroup( + groupID: groupID + ) + + // Ensures we have MLS valid key packages published otherwise the user can’t be added to any new groups. + await mlsService.uploadKeyPackagesIfNeeded() + + do { + // We need to resolve the now MLS 1:1 conversation with the other user + let otherUserQualifiedID = await conversationLocalStore.fetchOtherUserIDInOneOnOneConversation( + conversation: conversation + ) + + guard let otherUserQualifiedID else { + return + } + + try await oneOnOneResolver.resolveOneOnOneConversation(with: otherUserQualifiedID) + WireLogger.mls.debug("successfully resolved one on one conversation") + } catch { + WireLogger.mls.warn("failed to resolve one on one conversation: \(error)") + } + } } diff --git a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationMessageTimerUpdateEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationMessageTimerUpdateEventProcessor.swift index f121db3fad4..6e46afc521c 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationMessageTimerUpdateEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationMessageTimerUpdateEventProcessor.swift @@ -17,6 +17,7 @@ // import WireAPI +import WireDataModel /// Process conversation message timer update events. @@ -26,15 +27,49 @@ protocol ConversationMessageTimerUpdateEventProcessorProtocol { /// /// - Parameter event: A conversation message timer update event. - func processEvent(_ event: ConversationMessageTimerUpdateEvent) async throws + func processEvent(_ event: ConversationMessageTimerUpdateEvent) async } struct ConversationMessageTimerUpdateEventProcessor: ConversationMessageTimerUpdateEventProcessorProtocol { - func processEvent(_: ConversationMessageTimerUpdateEvent) async throws { - // TODO: [WPB-10171] - assertionFailure("not implemented yet") + let conversationLocalStore: any ConversationLocalStoreProtocol + let messageLocalStore: any MessageLocalStoreProtocol + + func processEvent(_ event: ConversationMessageTimerUpdateEvent) async { + let userID = event.senderID + let conversationID = event.conversationID + let timerInMilliseconds = Double(event.newTimer ?? 0) + let timestamp = event.timestamp + + let conversation = await conversationLocalStore.fetchOrCreateConversation( + id: conversationID.uuid, + domain: conversationID.domain + ) + + let timeoutValue = timerInMilliseconds / 1000 + let timeout: MessageDestructionTimeoutValue = .init(rawValue: timeoutValue) + let currentTimeout = await conversationLocalStore.conversationMessageDestructionTimeout(conversation) + + if currentTimeout != timeout { + + let messageType: MessageType = .messageTimerUpdate( + sender: (userID.uuid, userID.domain), + date: timestamp, + timeoutValue: timeoutValue + ) + + await messageLocalStore.addSystemMessageToConversation( + messageType: messageType, + conversationID: conversationID.uuid, + conversationDomain: conversationID.domain + ) + } + + await conversationLocalStore.storeConversation( + timeoutValue: timeoutValue, + for: conversation + ) } } diff --git a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationReceiptModeUpdateEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationReceiptModeUpdateEventProcessor.swift index 7ef16b24f3e..1e2a473d938 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationReceiptModeUpdateEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationReceiptModeUpdateEventProcessor.swift @@ -17,6 +17,7 @@ // import WireAPI +import WireLogging import WireSystem /// Process conversation receipt mode update events. @@ -36,11 +37,12 @@ struct ConversationReceiptModeUpdateEventProcessor: ConversationReceiptModeUpdat let userRepository: any UserRepositoryProtocol let conversationRepository: any ConversationRepositoryProtocol let conversationLocalStore: any ConversationLocalStoreProtocol + let messageRepository: any MessageRepositoryProtocol func processEvent(_ event: ConversationReceiptModeUpdateEvent) async throws { let senderID = event.senderID let conversationID = event.conversationID - let isEnabled = event.newRecieptMode == 1 + let isEnabled = event.newReceiptMode == 1 let sender = try await userRepository.fetchUser( id: senderID.uuid, @@ -69,20 +71,17 @@ struct ConversationReceiptModeUpdateEventProcessor: ConversationReceiptModeUpdat timestamp: .now ) - await conversationRepository.addSystemMessage( - systemMessage, - to: conversation + let systemMessageType: MessageType = .readReceiptsStatus( + isEnabled: isEnabled, + sender: (senderID.uuid, senderID.domain), + date: .now ) - let isConversationArchived = await conversationLocalStore.isConversationArchived(conversation) - let mutedMessageTypes = await conversationLocalStore.conversationMutedMessageTypes(conversation) - - if isConversationArchived, mutedMessageTypes == .none { - await conversationLocalStore.storeConversation( - isArchived: false, - for: conversation - ) - } + await messageRepository.addMessageToConversation( + messageType: systemMessageType, + conversationID: conversationID.uuid, + conversationDomain: conversationID.domain + ) } } diff --git a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationRenameEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationRenameEventProcessor.swift index 1dad0b00399..6d9f27c2a39 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationRenameEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationRenameEventProcessor.swift @@ -32,9 +32,22 @@ protocol ConversationRenameEventProcessorProtocol { struct ConversationRenameEventProcessor: ConversationRenameEventProcessorProtocol { - func processEvent(_: ConversationRenameEvent) async throws { - // TODO: [WPB-10177] - assertionFailure("not implemented yet") + let repository: any ConversationRepositoryProtocol + + func processEvent(_ event: ConversationRenameEvent) async throws { + let newName = event.newName + let conversationID = event.conversationID + let senderID = event.senderID + let timestamp = event.timestamp + + await repository.updateConversationName( + newName: newName, + conversationID: conversationID.uuid, + conversationDomain: conversationID.domain, + senderID: senderID.uuid, + senderDomain: senderID.domain, + date: timestamp + ) } } diff --git a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor/ConversationTypingEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor/ConversationTypingEventProcessor.swift new file mode 100644 index 00000000000..bd6fb200e56 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor/ConversationTypingEventProcessor.swift @@ -0,0 +1,131 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireAPI +import WireDataModel + +/// Process conversation typing events. + +protocol ConversationTypingEventProcessorProtocol { + + /// Process a conversation typing event. + /// + /// - Parameter event: A conversation typing event. + + func processEvent(_ event: ConversationTypingEvent) async + +} + +struct ConversationTypingEventProcessor: ConversationTypingEventProcessorProtocol { + + let conversationRepository: any ConversationRepositoryProtocol + let conversationLocalStore: any ConversationLocalStoreProtocol + let userRepository: any UserRepositoryProtocol + + private let typingUsersTimeout = ConversationTypingUsersTimeout() + + func processEvent(_ event: ConversationTypingEvent) async { + let conversationID = event.conversationID + let senderID = event.senderID + let isTyping = event.isTyping + + let user = await userRepository.fetchOrCreateUser( + id: senderID.uuid, + domain: senderID.domain + ) + + let conversation = await conversationRepository.fetchOrCreateConversation( + id: conversationID.uuid, + domain: conversationID.domain + ) + + // Since we'll be manipulating managed object IDs in `ConversationTypingUsersTimeout` + // we need to make sure we have valid, consistent IDs for the user and conversation. + conversationLocalStore.obtainPermanentIDs( + user: user, + conversation: conversation + ) + + let userObjectID = user.objectID + let conversationObjectID = conversation.objectID + + let wasTyping = typingUsersTimeout.contains( + userObjectID, + for: conversationObjectID + ) + + if isTyping { + let timeout = ConversationTypingUsersTimeout.defaultTimeout // 60 sec + + // Tracks the typing user timeout for a given conversation + typingUsersTimeout.add( + userObjectID, + for: conversationObjectID, + withTimeout: Date(timeIntervalSinceNow: timeout) + ) + } + + // Typing status changed + if wasTyping != isTyping { + if !isTyping { + // User is no longer typing, untracking him + typingUsersTimeout.remove( + userObjectID, + for: conversationObjectID + ) + } + + let userObjectIDs = typingUsersTimeout.userIds( + in: conversationObjectID + ) + + let typingUsersInfo = ConversationTypingUsersInfo( + users: userObjectIDs, + conversationID: conversationObjectID + ) + + // Updates non timed out typing users + await conversationRepository.updateTypingUsers([typingUsersInfo]) + } + + typingUsersTimeout.timerFiredCallback = timerDidFire + + typingUsersTimeout.updateExpirationIfNeeded() + } + + /// Called when timer from `ConversationTypingUsersTimeout` fires. + /// Untrack timed out typing users and updates valid (non timed out) typing users. + /// Callback is fired when a new timeout value is reached in `timeouts` dictionary. + + private func timerDidFire() async { + let conversationObjectIDs = typingUsersTimeout.pruneConversationsThatHaveTimoutBefore( + date: .now + ) + + // Map typing users for each conversation + let typingUsersInfo: [ConversationTypingUsersInfo] = conversationObjectIDs.map { + let userObjectIDs = typingUsersTimeout.userIds(in: $0) + return .init(users: userObjectIDs, conversationID: $0) + } + + await conversationRepository.updateTypingUsers(typingUsersInfo) + + typingUsersTimeout.updateExpirationIfNeeded() + } + +} diff --git a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor/ConversationTypingUsersInfo.swift b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor/ConversationTypingUsersInfo.swift new file mode 100644 index 00000000000..3043facbe5b --- /dev/null +++ b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor/ConversationTypingUsersInfo.swift @@ -0,0 +1,25 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import CoreData + +/// The typing users for a given conversation +public struct ConversationTypingUsersInfo { + let users: Set + let conversationID: NSManagedObjectID +} diff --git a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor/ConversationTypingUsersTimeout.swift b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor/ConversationTypingUsersTimeout.swift new file mode 100644 index 00000000000..216f8e27b29 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessor/ConversationTypingUsersTimeout.swift @@ -0,0 +1,117 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel + +/// Keeping track of typing users timeouts +final class ConversationTypingUsersTimeout: NSObject, ZMTimerClient { + + struct Key: Hashable { + let userObjectID: NSManagedObjectID + let conversationObjectID: NSManagedObjectID + } + + static let defaultTimeout: TimeInterval = 60 + + private var timeouts = [Key: Date]() + private var nextPruneDate: Date? + private var expirationTimer: ZMTimer? + + typealias VoidClosure = () async -> Void + var timerFiredCallback: VoidClosure? + + var firstTimeout: Date? { + timeouts.values.min() + } + + func add( + _ user: NSManagedObjectID, + for conversation: NSManagedObjectID, + withTimeout timeout: Date + ) { + let key = Key(userObjectID: user, conversationObjectID: conversation) + timeouts[key] = timeout + } + + func remove( + _ user: NSManagedObjectID, + for conversation: NSManagedObjectID + ) { + let key = Key(userObjectID: user, conversationObjectID: conversation) + timeouts.removeValue(forKey: key) + } + + func contains( + _ user: NSManagedObjectID, + for conversation: NSManagedObjectID + ) -> Bool { + let key = Key(userObjectID: user, conversationObjectID: conversation) + return timeouts[key] != nil + } + + func userIds( + in conversation: NSManagedObjectID + ) -> Set { + let userIds = timeouts.keys + .filter { $0.conversationObjectID == conversation } + .map(\.userObjectID) + + return Set(userIds) + } + + func pruneConversationsThatHaveTimoutBefore( + date pruneDate: Date + ) -> Set { + let keysToRemove = timeouts + .filter { $0.value < pruneDate } + .keys + + keysToRemove.forEach { + timeouts.removeValue(forKey: $0) + } + + return Set(keysToRemove.map(\.conversationObjectID)) + } + + /// Updates next prune date and fire a timer at that specific date. + /// When timer fires, the provided callback will be called - see `timerDidFire` + + func updateExpirationIfNeeded() { + guard + let date = firstTimeout, + date != nextPruneDate + else { + return + } + + expirationTimer?.cancel() + expirationTimer = ZMTimer(target: self) + expirationTimer?.fire(at: date) + nextPruneDate = date + } + + func timerDidFire(_ timer: ZMTimer!) { + guard timer === expirationTimer else { + return + } + + Task { [timerFiredCallback] in + await timerFiredCallback?() + } + } +} diff --git a/WireDomain/Sources/WireDomain/Event Processing/TeamEventProcessor/TeamMemberLeaveEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/TeamEventProcessor/TeamMemberLeaveEventProcessor.swift index ef7f131dd05..2f4ea790162 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/TeamEventProcessor/TeamMemberLeaveEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/TeamEventProcessor/TeamMemberLeaveEventProcessor.swift @@ -37,9 +37,9 @@ struct TeamMemberLeaveEventProcessor: TeamMemberLeaveEventProcessorProtocol { func processEvent(_ event: TeamMemberLeaveEvent) async throws { try await repository.deleteMembership( - for: event.userID, + userID: event.userID, domain: nil, - at: event.time + date: event.time ) } diff --git a/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserClientAddEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserClientAddEventProcessor.swift index 90e0f675c18..15e6b6b95ae 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserClientAddEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserClientAddEventProcessor.swift @@ -41,11 +41,11 @@ struct UserClientAddEventProcessor: UserClientAddEventProcessorProtocol { func processEvent(_ event: UserClientAddEvent) async throws { do { let localUserClient = try await repository.fetchOrCreateClient( - with: event.client.id + id: event.client.id ) try await repository.updateClient( - with: event.client.id, + id: event.client.id, from: event.client, isNewClient: localUserClient.isNew ) diff --git a/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserConnectionEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserConnectionEventProcessor.swift index 2d50ea45361..40cf367e47c 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserConnectionEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserConnectionEventProcessor.swift @@ -42,7 +42,7 @@ struct UserConnectionEventProcessor: UserConnectionEventProcessorProtocol { try await connectionsRepository.updateConnection(connection) if connection.status == .accepted { - try await oneOnOneResolver.invoke() + try await oneOnOneResolver.resolveAllOneOnOneConversations() } } } diff --git a/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserEventProcessor.swift index ccae8373400..894e1513067 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserEventProcessor.swift @@ -59,7 +59,7 @@ struct UserEventProcessor { case let .connection(event): try await connectionEventProcessor.processEvent(event) - case let .contactJoin(event): + case .contactJoin: /// This event is not processed, we only show a notification to the user. break @@ -79,7 +79,7 @@ struct UserEventProcessor { try await propertiesSetEventProcessor.processEvent(event) case let .propertiesDelete(event): - try await propertiesDeleteEventProcessor.processEvent(event) + await propertiesDeleteEventProcessor.processEvent(event) case .pushRemove: pushRemoveEventProcessor.processEvent() diff --git a/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserPropertiesDeleteEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserPropertiesDeleteEventProcessor.swift index 26272f0d291..df38a0613ad 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserPropertiesDeleteEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserPropertiesDeleteEventProcessor.swift @@ -17,6 +17,7 @@ // import WireAPI +import WireLogging import WireSystem /// Process user properties delete events. diff --git a/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsLocalStore.swift index f93935b752b..4351a15a002 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsLocalStore.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsLocalStore.swift @@ -16,15 +16,16 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import CoreData -import Foundation -import WireAPI import WireDataModel -protocol ConnectionsLocalStoreProtocol { +// sourcery: AutoMockable +public protocol ConnectionsLocalStoreProtocol { + + /// Save connection and related objects to local storage. + /// - Parameter connectionInfo: connection object func storeConnection( - _ connectionPayload: Connection + _ connectionInfo: ConnectionInfo ) async throws } @@ -44,15 +45,12 @@ final class ConnectionsLocalStore: ConnectionsLocalStoreProtocol { // MARK: - Public - /// Save connection and related objects to local storage. - /// - Parameter connectionPayload: connection object from WireAPI - - public func storeConnection(_ connectionPayload: Connection) async throws { + public func storeConnection(_ connectionInfo: ConnectionInfo) async throws { try await context.perform { [self] in - let connection = try storedConnection(from: connectionPayload) + let connection = try storedConnection(from: connectionInfo) - let conversation = try storedConversation(from: connectionPayload, with: connection) + let conversation = try storedConversation(from: connectionInfo, with: connection) connection.to.oneOnOneConversation = conversation @@ -67,7 +65,7 @@ final class ConnectionsLocalStore: ConnectionsLocalStoreProtocol { /// - Returns: conversation object stored locally private func storedConversation( - from connection: Connection, + from connection: ConnectionInfo, with storedConnection: ZMConnection ) throws -> ZMConversation { guard let conversationID = connection.conversationID ?? connection.qualifiedConversationID?.uuid else { @@ -90,7 +88,9 @@ final class ConnectionsLocalStore: ConnectionsLocalStoreProtocol { /// - Parameter connection: connection payload from WireAPI /// - Returns: connection object stored locally - private func storedConnection(from connection: Connection) throws -> ZMConnection { + private func storedConnection( + from connection: ConnectionInfo + ) throws -> ZMConnection { guard let userID = connection.receiverID ?? connection.receiverQualifiedID?.uuid else { throw ConnectionsRepositoryError.missingReceiverId } @@ -101,7 +101,7 @@ final class ConnectionsLocalStore: ConnectionsLocalStoreProtocol { in: context ) - storedConnection.status = connection.status.toDomainModel() + storedConnection.status = connection.status storedConnection.lastUpdateDateInGMT = connection.lastUpdate return storedConnection } diff --git a/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsModelMappings.swift b/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsModelMappings.swift index f9a778101a0..78fe079a49f 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsModelMappings.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsModelMappings.swift @@ -41,3 +41,18 @@ extension WireAPI.ConnectionStatus { } } + +extension WireAPI.Connection { + + func toDomainModel() -> ConnectionInfo { + .init( + senderID: senderID, + receiverID: receiverID, + receiverQualifiedID: receiverQualifiedID?.toDomainModel(), + conversationID: conversationID, + qualifiedConversationID: qualifiedConversationID?.toDomainModel(), + lastUpdate: lastUpdate, + status: status.toDomainModel() + ) + } +} diff --git a/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsRepository.swift b/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsRepository.swift index be089b4c7c6..07ccf94aa7c 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsRepository.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Connections/ConnectionsRepository.swift @@ -70,7 +70,7 @@ public struct ConnectionsRepository: ConnectionsRepositoryProtocol { await withThrowingTaskGroup(of: Void.self) { taskGroup in for connection in connections { taskGroup.addTask { - try await connectionsLocalStore.storeConnection(connection) + try await connectionsLocalStore.storeConnection(connection.toDomainModel()) } } } @@ -80,7 +80,7 @@ public struct ConnectionsRepository: ConnectionsRepositoryProtocol { public func updateConnection( _ connection: Connection ) async throws { - try await connectionsLocalStore.storeConnection(connection) + try await connectionsLocalStore.storeConnection(connection.toDomainModel()) } } diff --git a/WireDomain/Sources/WireDomain/Repositories/Connections/Models/ConnectionInfo.swift b/WireDomain/Sources/WireDomain/Repositories/Connections/Models/ConnectionInfo.swift new file mode 100644 index 00000000000..4b644cb7e35 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/Connections/Models/ConnectionInfo.swift @@ -0,0 +1,29 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel + +public struct ConnectionInfo: Sendable { + public let senderID: UUID? + public let receiverID: UUID? + public let receiverQualifiedID: WireDataModel.QualifiedID? + public let conversationID: UUID? + public let qualifiedConversationID: WireDataModel.QualifiedID? + public let lastUpdate: Date + public let status: WireDataModel.ZMConnectionStatus +} diff --git a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Group.swift b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Group.swift index e4ce2049149..ea1841b4e06 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Group.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Group.swift @@ -26,16 +26,16 @@ extension ConversationLocalStore { // MARK: - User & Role func fetchUserAndRole( - from remoteConversationMember: WireAPI.Conversation.Member, + from conversationMember: Conversation.Members.Member, for localConversation: ZMConversation ) -> (user: ZMUser, role: Role?)? { - guard let userID = remoteConversationMember.id ?? remoteConversationMember.qualifiedID?.uuid else { + guard let userID = conversationMember.id ?? conversationMember.qualifiedID?.uuid else { return nil } let user = ZMUser.fetchOrCreate( with: userID, - domain: remoteConversationMember.qualifiedID?.domain, + domain: conversationMember.qualifiedID?.domain, in: context ) @@ -50,8 +50,11 @@ extension ConversationLocalStore { ) } - let role = remoteConversationMember.conversationRole.map { - fetchOrCreateRoleForConversation(name: $0, conversation: localConversation) + let role = conversationMember.conversationRole.map { + fetchOrCreateRoleForConversation( + name: $0, + conversation: localConversation + ) } return (user, role) @@ -60,10 +63,10 @@ extension ConversationLocalStore { // MARK: - Members func updateMembers( - from remoteConversation: WireAPI.Conversation, + from conversation: Conversation, for localConversation: ZMConversation ) { - guard let members = remoteConversation.members else { + guard let members = conversation.members else { return } @@ -79,7 +82,10 @@ extension ConversationLocalStore { for: localConversation )?.role - localConversation.updateMembers(otherMembers, selfUserRole: selfUserRole) + localConversation.updateMembers( + otherMembers, + selfUserRole: selfUserRole + ) } // MARK: - 1:1 diff --git a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+MLS.swift b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+MLS.swift index 36a7c3aa5ee..0531f14c025 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+MLS.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+MLS.swift @@ -26,24 +26,24 @@ extension ConversationLocalStore { // MARK: - Message protocols func assignMessageProtocol( - from remoteConversation: WireAPI.Conversation, + from conversation: Conversation, for localConversation: ZMConversation ) { - guard let newMessageProtocol = remoteConversation.messageProtocol else { + guard let newMessageProtocol = conversation.messageProtocol else { eventProcessingLogger.warn( "message protocol is missing" ) return } - localConversation.messageProtocol = newMessageProtocol.toDomainModel() + localConversation.messageProtocol = newMessageProtocol } func updateMessageProtocol( - from remoteConversation: WireAPI.Conversation, + from conversation: Conversation, for localConversation: ZMConversation ) { - guard let newMessageProtocol = remoteConversation.messageProtocol else { + guard let newMessageProtocol = conversation.messageProtocol else { eventProcessingLogger.warn( "message protocol is missing" ) @@ -59,12 +59,16 @@ extension ConversationLocalStore { break /// no update, ignore case .mixed: localConversation.appendMLSMigrationStartedSystemMessage(sender: sender, at: .now) - localConversation.messageProtocol = newMessageProtocol.toDomainModel() + localConversation.messageProtocol = newMessageProtocol case .mls: let date = localConversation.lastModifiedDate ?? .now - localConversation.appendMLSMigrationPotentialGapSystemMessage(sender: sender, at: date) - localConversation.messageProtocol = newMessageProtocol.toDomainModel() + + localConversation.appendMLSMigrationPotentialGapSystemMessage( + sender: sender, at: date + ) + + localConversation.messageProtocol = newMessageProtocol } case .mixed: @@ -78,7 +82,7 @@ extension ConversationLocalStore { break /// no update, ignore case .mls: localConversation.appendMLSMigrationFinalizedSystemMessage(sender: sender, at: .now) - localConversation.messageProtocol = newMessageProtocol.toDomainModel() + localConversation.messageProtocol = newMessageProtocol } case .mls: @@ -119,8 +123,14 @@ extension ConversationLocalStore { ) if await context.perform({ localConversation.epoch <= 0 }) { - let ciphersuite = try await mlsService.createSelfGroup(for: groupID) - await context.perform { localConversation.ciphersuite = ciphersuite } + let ciphersuite = try await mlsService.createSelfGroup( + for: groupID + ) + + await context.perform { + localConversation.ciphersuite = ciphersuite + } + } else if try await !mlsService.conversationExists(groupID: groupID) { try await mlsService.joinGroup(with: groupID) } diff --git a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Metadata.swift b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Metadata.swift index f669ac6c9a1..99ea4375ad5 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Metadata.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Metadata.swift @@ -26,25 +26,25 @@ extension ConversationLocalStore { // MARK: - Metadata func updateMetadata( - from remoteConversation: WireAPI.Conversation, + from conversation: Conversation, for localConversation: ZMConversation ) { - if let teamID = remoteConversation.teamID { + if let teamID = conversation.teamID { localConversation.updateTeam(identifier: teamID) } - if let name = remoteConversation.name { + if let name = conversation.name { localConversation.userDefinedName = name } - guard let userID = remoteConversation.creator else { + guard let userID = conversation.creator else { return } /// We assume that the creator always belongs to the same domain as the conversation let creator = ZMUser.fetchOrCreate( with: userID, - domain: remoteConversation.qualifiedID?.domain, + domain: conversation.qualifiedID?.domain, in: context ) @@ -54,28 +54,28 @@ extension ConversationLocalStore { // MARK: - Attributes func updateAttributes( - from remoteConversation: WireAPI.Conversation, + from conversation: Conversation, for localConversation: ZMConversation, isFederationEnabled: Bool ) { - localConversation.domain = isFederationEnabled ? remoteConversation.qualifiedID?.domain : nil + localConversation.domain = isFederationEnabled ? conversation.qualifiedID?.domain : nil localConversation.needsToBeUpdatedFromBackend = false - if let epoch = remoteConversation.epoch { + if let epoch = conversation.epoch { localConversation.epoch = UInt64(epoch) } - let base64String = remoteConversation.mlsGroupID + let base64String = conversation.mlsGroupID if let base64String, let mlsGroupID = MLSGroupID(base64Encoded: base64String) { localConversation.mlsGroupID = mlsGroupID } - let ciphersuite = remoteConversation.cipherSuite - let epoch = remoteConversation.epoch + let ciphersuite = conversation.cipherSuite + let epoch = conversation.epoch if let ciphersuite, let epoch, epoch > 0 { - localConversation.ciphersuite = ciphersuite.toDomainModel() + localConversation.ciphersuite = ciphersuite } } diff --git a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Status.swift b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Status.swift index 7e4b72f6359..2c578a70b74 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Status.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore+Status.swift @@ -26,7 +26,7 @@ extension ConversationLocalStore { // MARK: - Conversation status func updateConversationStatus( - from remoteConversation: WireAPI.Conversation, + from remoteConversation: Conversation, for localConversation: ZMConversation ) { if let readReceiptMode = remoteConversation.readReceiptMode { @@ -36,13 +36,13 @@ extension ConversationLocalStore { if let accessModes = remoteConversation.access { if let accessRoles = remoteConversation.accessRoles { localConversation.updateAccessStatus( - accessModes: accessModes.map(\.rawValue), - accessRoles: accessRoles.map(\.rawValue) + accessModes: accessModes, + accessRoles: accessRoles ) } else if let accessRole = remoteConversation.legacyAccessRole { - let accessRoles = ConversationAccessRoleV2.fromLegacyAccessRole(accessRole.toDomainModel()) + let accessRoles = ConversationAccessRoleV2.fromLegacyAccessRole(accessRole) localConversation.updateAccessStatus( - accessModes: accessModes.map(\.rawValue), + accessModes: accessModes, accessRoles: accessRoles.map(\.rawValue) ) } @@ -56,14 +56,17 @@ extension ConversationLocalStore { // MARK: - MLS status func updateMLSStatus( - from remoteConversation: WireAPI.Conversation, - for localConversation: ZMConversation + from remoteConversation: Conversation, + for localConversation: ZMConversation, + isMLSEnabled: Bool ) async { - guard DeveloperFlag.enableMLSSupport.isOn else { return } + guard isMLSEnabled else { return } await updateConversationIfNeeded( localConversation: localConversation, - fallbackGroupID: remoteConversation.mlsGroupID.map { .init(base64Encoded: $0) } ?? nil + fallbackGroupID: remoteConversation.mlsGroupID.map { + .init(base64Encoded: $0) + } ?? nil ) } @@ -96,7 +99,9 @@ extension ConversationLocalStore { let conversationExists: Bool do { - conversationExists = try await mlsService.conversationExists(groupID: mlsGroupID) + conversationExists = try await mlsService.conversationExists( + groupID: mlsGroupID + ) } catch { conversationExists = false } @@ -108,5 +113,4 @@ extension ConversationLocalStore { context.saveOrRollback() } } - } diff --git a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore.swift index cbb1ca47fb4..3aaa338a9ba 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationLocalStore.swift @@ -17,8 +17,8 @@ // import CoreData -import WireAPI import WireDataModel +import WireLogging // sourcery: AutoMockable /// A local store dedicated to conversations. @@ -49,26 +49,31 @@ public protocol ConversationLocalStoreProtocol { /// - Parameter isFederationEnabled: A flag indicating whether a `Federation` is enabled. func storeConversation( - _ conversation: WireAPI.Conversation, + _ conversation: WireDomain.Conversation, timestamp: Date, - isFederationEnabled: Bool + isFederationEnabled: Bool, + isMLSEnabled: Bool ) async /// Stores a flag indicating whether a conversation requires an update from backend. /// - Parameter needsBackendUpdate: A flag indicated whether the qualified conversation needs to be updated from /// backend. - /// - Parameter qualifiedId: The conversation qualified ID. + /// - Parameter conversationID: The conversation ID. + /// - Parameter conversationDomain: The conversation domain. func storeConversation( needsBackendUpdate: Bool, - qualifiedId: WireAPI.QualifiedID + conversationID: UUID, + conversationDomain: String ) async /// Stores a given failed conversation locally. - /// - Parameter qualifiedId: The conversation qualified ID. + /// - Parameter conversationID: The conversation ID. + /// - Parameter conversationDomain: The conversation domain. func storeFailedConversation( - withQualifiedId qualifiedId: WireAPI.QualifiedID + conversationID: UUID, + conversationDomain: String ) async /// Fetches a MLS conversation locally. @@ -103,13 +108,15 @@ public protocol ConversationLocalStoreProtocol { /// Removes a given user from all group conversations. /// /// - parameters: - /// - user: The user to remove from the conversations. + /// - participantID: The ID of the participant to remove. + /// - participantDomain: The domain of the participant. /// - date: The date the user was removed from the conversations. func removeParticipantFromAllGroupConversations( - user: ZMUser, + participantID: UUID, + participantDomain: String?, date: Date - ) async + ) async throws /// Adds a participant or updates its role in a conversation. /// @@ -132,13 +139,13 @@ public protocol ConversationLocalStoreProtocol { /// - newParticipants: The id, domain and role of the new participant. /// - sender: The user who added the participants. /// - date: The date the participants were added. - /// - conversation: The conversation to add the participants + /// - conversation: The conversation to add the participants to. func addParticipants( _ participants: [(id: UUID, domain: String?, role: String?)], addedBy sender: (id: UUID, domain: String?), atDate date: Date, - to conversation: ZMConversation + conversation: (id: UUID, domain: String) ) async throws /// Updates the member muted and archived status. @@ -174,42 +181,6 @@ public protocol ConversationLocalStoreProtocol { for conversation: ZMConversation ) async -> WireDataModel.MessageProtocol - /// Adds a system message to a given conversation. - /// - parameters: - /// - message: The system message to add. - /// - conversation: The conversation to add the system message to. - - func addSystemMessage( - _ message: SystemMessage, - to conversation: ZMConversation - ) async - - /// Retrieves conversation muted message types - /// - parameter conversation: The conversation to get the muted message types for. - /// - returns: The muted message types. - - func conversationMutedMessageTypes( - _ conversation: ZMConversation - ) async -> MutedMessageTypes - - /// Stores a flag indicating whether a conversation is archived. - /// - parameters: - /// - isArchived: Indicates whether the conversation is archived. - /// - conversation: The conversation to set the `isArchived` flag for. - - func storeConversation( - isArchived: Bool, - for conversation: ZMConversation - ) async - - /// Indicates whether a conversation is archived. - /// - parameter conversation: The conversation to check the `isArchived` flag for. - /// - returns: A flag indicating whether the conversation is archived. - - func isConversationArchived( - _ conversation: ZMConversation - ) async -> Bool - /// Stores a flag indicating whether a conversation has read receipts enabled. /// - parameters: /// - hasReadReceiptsEnabled: A flag indicating whether the conversation has read receipts enabled. @@ -232,6 +203,24 @@ public protocol ConversationLocalStoreProtocol { initiatingUser: ZMUser ) async + /// The conversation active message destruction timeout value. + /// - Parameters: + /// - conversation: The conversation to get the message destruction timeout value from. + /// - returns: A `MessageDestructionTimeoutValue` object. + + func conversationMessageDestructionTimeout( + _ conversation: ZMConversation + ) async -> MessageDestructionTimeoutValue + + /// Stores a message destruction timeout value. + /// - parameters: + /// - timeoutValue: The message destruction timeout value. + /// - conversation: The conversation to update the value for. + + func storeConversation( + timeoutValue: Double, + for conversation: ZMConversation + ) async /// Fetches or creates a role locally. /// - Parameters: /// - role: The role name to fetch or create. @@ -292,6 +281,72 @@ public protocol ConversationLocalStoreProtocol { for conversation: ZMConversation ) async -> MLSGroupID? + /// Sends a notification using the main context informing typing users + /// have been updated for a given conversation. + /// - Parameters: + /// - conversationID: The conversation managed object ID. + /// - usersID: The updated typing users managed object IDs. + + func updateTypingUsers( + conversationID: NSManagedObjectID, + usersID: Set + ) async + + /// Obtain permanent stored object IDs. + /// - Parameters: + /// - user: The user to get the permanent managed object ID for. + /// - conversation: The conversation to get the permanent managed object ID for. + + func obtainPermanentIDs( + user: ZMUser, + conversation: ZMConversation + ) + + /// Fetches the current conversation name + /// - parameter conversation: The conversation to fetch the name for. + /// - returns: The conversation name + + func conversationName( + conversation: ZMConversation + ) async -> String? + + /// Updates the conversation name. + /// - Parameters: + /// - newName: The new name for the conversation. + /// - conversation: The conversation to update the name for. + + func storeConversation( + newName: String, + conversation: ZMConversation + ) async + + /// Updates or creates a MLS group locally. + /// - Parameters: + /// - groupID: The MLS group ID. + + func updateOrCreateMLSGroup( + groupID: MLSGroupID + ) async + + /// Stores the conversation MLS group ID and marks the mls status as ready. + /// - Parameters: + /// - mlsGroupID: The MLS group ID related to the conversation. + /// - conversation: The conversation to update the properties for. + + func storeMLSConversationEstablished( + mlsGroupID: MLSGroupID, + conversation: ZMConversation + ) async + + /// Fetches the other user qualified id (not self user) in a 1:1 conversation. + /// - Parameters: + /// - conversation: The 1:1 conversation self and other user should be part of. + /// - returns: The other user `QualifiedID`. + + func fetchOtherUserIDInOneOnOneConversation( + conversation: ZMConversation + ) async -> WireDataModel.QualifiedID? + } public final class ConversationLocalStore: ConversationLocalStoreProtocol { @@ -308,21 +363,79 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { let mlsLogger = WireLogger.mls let updateEventLogger = WireLogger.updateEvent let userLocalStore: any UserLocalStoreProtocol + let messageLocalStore: any MessageLocalStoreProtocol // MARK: - Object lifecycle public init( context: NSManagedObjectContext, mlsService: MLSServiceInterface, - userLocalStore: any UserLocalStoreProtocol + userLocalStore: any UserLocalStoreProtocol, + messageLocalStore: any MessageLocalStoreProtocol ) { self.context = context self.mlsService = mlsService self.userLocalStore = userLocalStore + self.messageLocalStore = messageLocalStore } // MARK: - Public + public func storeMLSConversationEstablished( + mlsGroupID: MLSGroupID, + conversation: ZMConversation + ) async { + await context.perform { + conversation.mlsStatus = .ready + conversation.mlsGroupID = mlsGroupID + } + } + + public func updateOrCreateMLSGroup( + groupID: MLSGroupID + ) async { + await context.perform { [context] in + + MLSGroup.updateOrCreate( + id: groupID, + inSyncContext: context + ) { + $0.lastKeyMaterialUpdate = .now + } + } + } + + public func fetchOtherUserIDInOneOnOneConversation( + conversation: ZMConversation + ) async -> WireDataModel.QualifiedID? { + await context.perform { + guard conversation.conversationType == .oneOnOne else { + WireLogger.conversation.info( + "conversation type is not expected 'oneOnOne', aborting." + ) + + return nil + } + + guard + let otherUser = conversation.localParticipantsExcludingSelf.first, + let otherUserID = otherUser.remoteIdentifier, + let otherUserDomain = otherUser.domain ?? BackendInfo.domain + else { + WireLogger.conversation.warn( + "failed to retrieve other user in 1:1 conversation" + ) + + return nil + } + + return QualifiedID( + uuid: otherUserID, + domain: otherUserDomain + ) + } + } + public func updateMemberStatus( mutedStatusInfo: (status: Int?, referenceDate: Date?), archivedStatusInfo: (status: Bool?, referenceDate: Date?), @@ -398,10 +511,24 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { _ participants: [(id: UUID, domain: String?, role: String?)], addedBy sender: (id: UUID, domain: String?), atDate date: Date, - to conversation: ZMConversation + conversation: (id: UUID, domain: String) ) async throws { typealias UserAndRole = (user: ZMUser, role: Role?) + let localConversation = await fetchConversation( + id: conversation.id, + domain: conversation.domain + ) + + guard let localConversation else { + return WireLogger.eventProcessing.error( + "Member join update missing conversation, aborting... ", + attributes: [ + .conversationId: conversation.id.safeForLoggingDescription + ] + ) + } + let usersAndRoles = await withTaskGroup(of: UserAndRole?.self) { taskGroup in for newParticipant in participants { taskGroup.addTask { [self] in @@ -413,7 +540,7 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { if let participantRole = newParticipant.role { let role = await fetchOrCreateRole( participantRole, - in: conversation + in: localConversation ) return (user, role) @@ -434,34 +561,71 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { let users = Set(usersAndRoles.map(\.user)) let existingUsers = await localParticipants( - in: conversation + in: localConversation ) let newUsers = users.subtracting(existingUsers) - if !newUsers.isEmpty, await isGroupConversation(conversation) { - let sender = try await userLocalStore.fetchUser( - id: sender.id, - domain: sender.domain - ) + if !newUsers.isEmpty, await isGroupConversation(localConversation) { - let systemMessage = SystemMessage( - type: .participantsAdded, + let systemMessageType: MessageType = .participantsAdded( + participants: participants.map { ($0.id, $0.domain) }, sender: sender, - users: newUsers, - clients: nil, - timestamp: date + date: date ) - await addSystemMessage(systemMessage, to: conversation) + await messageLocalStore.addSystemMessageToConversation( + messageType: systemMessageType, + conversationID: conversation.id, + conversationDomain: conversation.domain + ) } await context.perform { - conversation.addParticipantsAndUpdateConversationState( + localConversation.addParticipantsAndUpdateConversationState( usersAndRoles: usersAndRoles ) } } + public func updateTypingUsers( + conversationID: NSManagedObjectID, + usersID: Set + ) async { + await context.perform { [context] in + if let conversation = context.object(with: conversationID) as? ZMConversation { + + let users = usersID.compactMap { + context.object(with: $0) as? ZMUser + } + + context.typingUsers?.update( + typingUsers: Set(users), + in: conversation + ) + + self.notifyTypingUsers( + Set(users), + in: conversation + ) + } + } + } + + public func obtainPermanentIDs( + user: ZMUser, + conversation: ZMConversation + ) { + if user.objectID.isTemporaryID || conversation.objectID.isTemporaryID { + do { + try context.obtainPermanentIDs(for: [user, conversation]) + } catch { + WireLogger.eventProcessing.error( + "Failed to obtain permanent object ids: \(error.localizedDescription)" + ) + } + } + } + public func addSystemMessage( _ message: SystemMessage, to conversation: ZMConversation @@ -492,9 +656,10 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { } public func storeConversation( - _ conversation: WireAPI.Conversation, + _ conversation: Conversation, timestamp: Date, - isFederationEnabled: Bool + isFederationEnabled: Bool, + isMLSEnabled: Bool ) async { guard let conversationType = conversation.type else { return @@ -521,16 +686,17 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { switch conversationType { case .group: await updateOrCreateGroupConversation( - remoteConversation: conversation, - remoteConversationID: id, + from: conversation, + withID: id, serverTimestamp: timestamp, - isFederationEnabled: isFederationEnabled + isFederationEnabled: isFederationEnabled, + isMLSEnabled: isMLSEnabled ) case .`self`: await updateOrCreateSelfConversation( - remoteConversation: conversation, - remoteConversationID: id, + from: conversation, + withID: id, serverTimestamp: timestamp, isFederationEnabled: isFederationEnabled ) @@ -539,8 +705,8 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { /// Conversations are of type `connection` while the connection /// is pending. await updateOrCreateConnectionConversation( - remoteConversation: conversation, - remoteConversationID: id, + from: conversation, + withID: id, serverTimestamp: timestamp, isFederationEnabled: isFederationEnabled ) @@ -549,8 +715,8 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { /// Conversations are of type `oneOnOne` when the connection /// is accepted. await updateOrCreateOneToOneConversation( - remoteConversation: conversation, - remoteConversationID: id, + from: conversation, + withID: id, serverTimestamp: timestamp, isFederationEnabled: isFederationEnabled ) @@ -559,12 +725,13 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { public func storeConversation( needsBackendUpdate: Bool, - qualifiedId: WireAPI.QualifiedID + conversationID: UUID, + conversationDomain: String ) async { await context.perform { [context] in let conversation = ZMConversation.fetch( - with: qualifiedId.uuid, - domain: qualifiedId.domain, + with: conversationID, + domain: conversationDomain, in: context ) @@ -573,11 +740,12 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { } public func storeFailedConversation( - withQualifiedId qualifiedId: WireAPI.QualifiedID + conversationID: UUID, + conversationDomain: String ) async { let conversation = await fetchOrCreateConversation( - id: qualifiedId.uuid, - domain: qualifiedId.domain + id: conversationID, + domain: conversationDomain ) await context.perform { @@ -613,12 +781,23 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { } } + public func conversationMessageDestructionTimeout( + _ conversation: ZMConversation + ) async -> MessageDestructionTimeoutValue { + await context.perform { + conversation.activeMessageDestructionTimeoutValue ?? .init(rawValue: 0) + } + } + public func storeConversation( - isArchived: Bool, + timeoutValue: Double, for conversation: ZMConversation ) async { await context.perform { - conversation.isArchived = isArchived + conversation.setMessageDestructionTimeoutValue( + .init(rawValue: timeoutValue), + for: .groupConversation + ) } } @@ -648,9 +827,16 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { } public func removeParticipantFromAllGroupConversations( - user: ZMUser, + participantID: UUID, + participantDomain: String?, date: Date - ) async { + ) async throws { + + let user = try await userLocalStore.fetchUser( + id: participantID, + domain: participantDomain + ) + let allGroupConversations = await context.perform { // swiftformat:disable:next redundantProperty let allGroupConversations: [ZMConversation] = user.participantRoles.compactMap { @@ -665,33 +851,42 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { } for conversation in allGroupConversations { - let (userTeam, isTeamMember) = await context.perform { - (user.team, user.isTeamMember) + let (userTeam, isTeamMember, conversationTeam, conversationID, conversationDomain) = await context.perform { + ( + user.team, + user.isTeamMember, + conversation.team, + conversation.remoteIdentifier as UUID, + conversation.domain + ) } - let teamConversation = await context.perform { - conversation.team - } + if isTeamMember, conversationTeam == userTeam { - if isTeamMember, teamConversation == userTeam { - let systemMessage = SystemMessage( - type: .teamMemberLeave, - sender: user, - users: [user], - timestamp: date + let systemMessageType: MessageType = .teamMemberRemoved( + member: (participantID, participantDomain), + date: date ) - await addSystemMessage(systemMessage, to: conversation) + await messageLocalStore.addSystemMessageToConversation( + messageType: systemMessageType, + conversationID: conversationID, + conversationDomain: conversationDomain + ) } else { - let systemMessage = SystemMessage( - type: .participantsRemoved, - sender: user, - users: [user], - timestamp: date + + let systemMessageType: MessageType = .participantsRemoved( + participants: [(participantID, participantDomain)], + sender: (participantID, participantDomain), + date: date ) - await addSystemMessage(systemMessage, to: conversation) + await messageLocalStore.addSystemMessageToConversation( + messageType: systemMessageType, + conversationID: conversationID, + conversationDomain: conversationDomain + ) } await context.perform { @@ -786,52 +981,82 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { } } + public func conversationName( + conversation: ZMConversation + ) async -> String? { + await context.perform { + conversation.userDefinedName + } + } + + public func storeConversation( + newName: String, + conversation: ZMConversation + ) async { + await context.perform { + conversation.userDefinedName = newName + } + } + // MARK: - Private + private func notifyTypingUsers( + _ typingUsers: Set, + in conversation: ZMConversation + ) { + let typingNotificationUsersKey = "typingUsers" + + NotificationInContext( + name: .typingNotification, + context: context.notificationContext, + object: self, + userInfo: [typingNotificationUsersKey: typingUsers] + ).post() + } + /// Updates or creates a conversation of type `connection` locally. /// /// See and for more information. /// - /// - Parameter remoteConversation: The conversation object received from backend. - /// - Parameter removeConversationID: The conversation ID received from backend. + /// - Parameter conversation: The up-to-date conversation object. + /// - Parameter id: The conversation ID. /// - Parameter isFederationEnabled: A flag indicating whether a federation is enabled. private func updateOrCreateConnectionConversation( - remoteConversation: WireAPI.Conversation, - remoteConversationID: UUID, + from conversation: Conversation, + withID id: UUID, serverTimestamp: Date, isFederationEnabled: Bool ) async { - let conversation = await fetchOrCreateConversation( - id: remoteConversationID, - domain: remoteConversation.qualifiedID?.domain + let localConversation = await fetchOrCreateConversation( + id: id, + domain: conversation.qualifiedID?.domain ) await context.perform { [self] in - conversation.conversationType = .connection + localConversation.conversationType = .connection commonUpdate( - from: remoteConversation, - for: conversation, + from: conversation, + for: localConversation, serverTimestamp: serverTimestamp, isFederationEnabled: isFederationEnabled ) - assignMessageProtocol( - from: remoteConversation, - for: conversation + from: conversation, + for: localConversation ) updateConversationStatus( - from: remoteConversation, - for: conversation + from: conversation, + for: localConversation ) - conversation.needsToBeUpdatedFromBackend = false - conversation.isPendingInitialFetch = false + localConversation.needsToBeUpdatedFromBackend = false + localConversation.isPendingInitialFetch = false } - guard let selfMember = remoteConversation.members?.selfMember else { + guard let selfMember = conversation.members?.selfMember else { return } @@ -841,7 +1066,7 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { await updateMemberStatus( mutedStatusInfo: mutedStatusInfo, archivedStatusInfo: archivedStatusInfo, - for: conversation + for: localConversation ) } @@ -849,19 +1074,20 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { /// /// See and for more information. /// - /// - Parameter remoteConversation: The conversation object received from backend. - /// - Parameter removeConversationID: The conversation ID received from backend. + /// - Parameter conversation: The up-to-date conversation object. + /// - Parameter id: The conversation ID. /// - Parameter isFederationEnabled: A flag indicating whether a federation is enabled. private func updateOrCreateSelfConversation( - remoteConversation: WireAPI.Conversation, - remoteConversationID: UUID, + from conversation: Conversation, + withID id: UUID, serverTimestamp: Date, isFederationEnabled: Bool ) async { - let conversation = await fetchOrCreateConversation( - id: remoteConversationID, - domain: remoteConversation.qualifiedID?.domain + + let localConversation = await fetchOrCreateConversation( + id: id, + domain: conversation.qualifiedID?.domain ) let mlsGroupID = await context.perform { @@ -869,28 +1095,28 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { } await context.perform { [self] in - conversation.conversationType = .`self` - conversation.isPendingMetadataRefresh = false + localConversation.conversationType = .`self` + localConversation.isPendingMetadataRefresh = false commonUpdate( - from: remoteConversation, - for: conversation, + from: conversation, + for: localConversation, serverTimestamp: serverTimestamp, isFederationEnabled: isFederationEnabled ) updateMessageProtocol( - from: remoteConversation, - for: conversation + from: conversation, + for: localConversation ) - conversation.isPendingInitialFetch = false - conversation.needsToBeUpdatedFromBackend = false + localConversation.isPendingInitialFetch = false + localConversation.needsToBeUpdatedFromBackend = false } if mlsGroupID != nil { do { - try await createOrJoinSelfConversation(from: conversation) + try await createOrJoinSelfConversation(from: localConversation) } catch { mlsLogger.error( "createOrJoinSelfConversation threw error: \(String(reflecting: error))" @@ -903,83 +1129,88 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { /// /// See and for more information. /// - /// - Parameter remoteConversation: The conversation object received from backend. - /// - Parameter removeConversationID: The conversation ID received from backend. + /// - Parameter conversation: The up-to-date conversation object. + /// - Parameter id: The conversation ID. /// - Parameter isFederationEnabled: A flag indicating whether a federation is enabled. private func updateOrCreateGroupConversation( - remoteConversation: WireAPI.Conversation, - remoteConversationID: UUID, + from conversation: Conversation, + withID id: UUID, serverTimestamp: Date, - isFederationEnabled: Bool + isFederationEnabled: Bool, + isMLSEnabled: Bool ) async { var isInitialFetch = false - let conversation = await fetchOrCreateConversation( - id: remoteConversationID, - domain: remoteConversation.qualifiedID?.domain + let localConversation = await fetchOrCreateConversation( + id: id, + domain: conversation.qualifiedID?.domain ) await context.perform { [self] in - isInitialFetch = conversation.isPendingInitialFetch + isInitialFetch = localConversation.isPendingInitialFetch - conversation.conversationType = .group - conversation.remoteIdentifier = remoteConversationID - conversation.isPendingMetadataRefresh = false - conversation.isPendingInitialFetch = false + localConversation.conversationType = .group + localConversation.remoteIdentifier = id + localConversation.isPendingMetadataRefresh = false + localConversation.isPendingInitialFetch = false commonUpdate( - from: remoteConversation, - for: conversation, + from: conversation, + for: localConversation, serverTimestamp: serverTimestamp, isFederationEnabled: isFederationEnabled ) updateConversationStatus( - from: remoteConversation, - for: conversation + from: conversation, + for: localConversation ) if isInitialFetch { assignMessageProtocol( - from: remoteConversation, - for: conversation + from: conversation, + for: localConversation ) } else { updateMessageProtocol( - from: remoteConversation, - for: conversation + from: conversation, + for: localConversation ) } Flow.createGroup.checkpoint( - description: "conversation created remote id: \(conversation.remoteIdentifier?.safeForLoggingDescription ?? "")" + description: "conversation created remote id: \(localConversation.remoteIdentifier?.safeForLoggingDescription ?? "")" ) } - if let selfMember = remoteConversation.members?.selfMember { + if let selfMember = conversation.members?.selfMember { let mutedStatusInfo = (selfMember.mutedStatus, selfMember.mutedReference) let archivedStatusInfo = (selfMember.archived, selfMember.archivedReference) await updateMemberStatus( mutedStatusInfo: mutedStatusInfo, archivedStatusInfo: archivedStatusInfo, - for: conversation + for: localConversation ) } - await updateMLSStatus(from: remoteConversation, for: conversation) + await updateMLSStatus( + from: conversation, + for: localConversation, + isMLSEnabled: isMLSEnabled + ) await context.perform { [self] in if isInitialFetch { /// we just got a new conversation, we display new conversation header - conversation.appendNewConversationSystemMessage( + localConversation.appendNewConversationSystemMessage( at: .distantPast, - users: conversation.localParticipants + users: localConversation.localParticipants ) /// Slow synced conversations should be considered read from the start - conversation.lastReadServerTimeStamp = conversation.lastModifiedDate + localConversation.lastReadServerTimeStamp = localConversation.lastModifiedDate Flow.createGroup.checkpoint( description: "new system message for conversation inserted" @@ -988,7 +1219,7 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { /// If we discover this group is actually a fake one on one, /// then we should link the one on one user. - linkOneOnOneUserIfNeeded(for: conversation) + linkOneOnOneUserIfNeeded(for: localConversation) } } @@ -996,23 +1227,23 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { /// /// See and for more information. /// - /// - Parameter remoteConversation: The conversation object received from backend. - /// - Parameter removeConversationID: The conversation ID received from backend. + /// - Parameter conversation: The up-to-date conversation object. + /// - Parameter id: The conversation ID. /// - Parameter isFederationEnabled: A flag indicating whether a federation is enabled. private func updateOrCreateOneToOneConversation( - remoteConversation: WireAPI.Conversation, - remoteConversationID: UUID, + from conversation: Conversation, + withID id: UUID, serverTimestamp: Date, isFederationEnabled: Bool ) async { - guard let conversationTypeRawValue = remoteConversation.type?.rawValue else { + guard let conversationTypeRawValue = conversation.type?.rawValue else { return } - let conversation = await fetchOrCreateConversation( - id: remoteConversationID, - domain: remoteConversation.qualifiedID?.domain + let localConversation = await fetchOrCreateConversation( + id: id, + domain: conversation.qualifiedID?.domain ) await context.perform { [self] in @@ -1020,40 +1251,40 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { rawValue: conversationTypeRawValue ) - if conversation.oneOnOneUser?.connection?.status == .sent { - conversation.conversationType = .connection + if localConversation.oneOnOneUser?.connection?.status == .sent { + localConversation.conversationType = .connection } else { - conversation.conversationType = conversationType + localConversation.conversationType = conversationType } assignMessageProtocol( - from: remoteConversation, - for: conversation + from: conversation, + for: localConversation ) commonUpdate( - from: remoteConversation, - for: conversation, + from: conversation, + for: localConversation, serverTimestamp: serverTimestamp, isFederationEnabled: isFederationEnabled ) - linkOneOnOneUserIfNeeded(for: conversation) + linkOneOnOneUserIfNeeded(for: localConversation) - conversation.needsToBeUpdatedFromBackend = false - conversation.isPendingInitialFetch = false + localConversation.needsToBeUpdatedFromBackend = false + localConversation.isPendingInitialFetch = false updateConversationStatus( - from: remoteConversation, - for: conversation + from: conversation, + for: localConversation ) - if let otherUser = conversation.localParticipantsExcludingSelf.first { - conversation.isPendingMetadataRefresh = otherUser.isPendingMetadataRefresh + if let otherUser = localConversation.localParticipantsExcludingSelf.first { + localConversation.isPendingMetadataRefresh = otherUser.isPendingMetadataRefresh } } - guard let selfMember = remoteConversation.members?.selfMember else { + guard let selfMember = conversation.members?.selfMember else { return } @@ -1063,36 +1294,36 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { await updateMemberStatus( mutedStatusInfo: mutedStatusInfo, archivedStatusInfo: archivedStatusInfo, - for: conversation + for: localConversation ) } /// A common update method for all conversations received, no matter the type of the conversation. /// - /// - Parameter remoteConversation: The conversation object received from backend. + /// - Parameter conversation: The up-to-date conversation object. /// - Parameter localConversation: The local conversation to update. /// - Parameter isFederationEnabled: A flag indicating whether a federation is enabled. /// - Parameter serverTimestamp: The date the conversation was created/updated. private func commonUpdate( - from remoteConversation: WireAPI.Conversation, + from conversation: Conversation, for localConversation: ZMConversation, serverTimestamp: Date, isFederationEnabled: Bool ) { updateAttributes( - from: remoteConversation, + from: conversation, for: localConversation, isFederationEnabled: isFederationEnabled ) updateMetadata( - from: remoteConversation, + from: conversation, for: localConversation ) updateMembers( - from: remoteConversation, + from: conversation, for: localConversation ) @@ -1106,7 +1337,7 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { /// block. /// /// - Parameter conversationID: The conversation ID to fetch or create the local conversation from. - /// - Parameter domain: The domain to fetch or create the conversation from. + /// - Parameter conversationDomain: The domain to fetch or create the conversation from. /// - Parameter handler: A completion block that takes a `ZMConversation` as argument and returns /// a `ZMConversation` and an optional `MLSGroupID`. /// @@ -1116,12 +1347,13 @@ public final class ConversationLocalStore: ConversationLocalStoreProtocol { @discardableResult private func fetchOrCreateConversation( conversationID: UUID, - domain: String?, + conversationDomain: String?, handler: @escaping (ZMConversation) -> (ZMConversation, MLSGroupID?) ) async -> (ZMConversation, MLSGroupID?) { + let conversation = await fetchOrCreateConversation( id: conversationID, - domain: domain + domain: conversationDomain ) return await context.perform { diff --git a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationModelMappings.swift b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationModelMappings.swift index 0fcf229f41b..b58b57de33f 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationModelMappings.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationModelMappings.swift @@ -76,10 +76,87 @@ extension WireAPI.ConversationMemberLeaveReason { func toDomainModel() -> ZMSystemMessageType { switch self { - case .userDeleted, .left: + case .userDeleted, .userLeft: .teamMemberLeave - case .removed: + case .userRemoved: .participantsRemoved } } } + +extension WireAPI.ConversationType { + + func toDomainModel() -> WireDataModel.BackendConversationType { + switch self { + case .group: + .group + case .self: + .`self` + case .oneOnOne: + .oneOnOne + case .connection: + .connection + } + } + +} + +extension WireAPI.Conversation.Members { + + func toDomainModel() -> WireDomain.Conversation.Members { + .init( + others: others.map { $0.toDomainModel() }, + selfMember: selfMember.toDomainModel() + ) + } + +} + +extension WireAPI.Conversation.Member { + + func toDomainModel() -> WireDomain.Conversation.Members.Member { + .init( + qualifiedID: qualifiedID?.toDomainModel(), + id: id, + qualifiedTarget: qualifiedTarget?.toDomainModel(), + target: target, + conversationRole: conversationRole, + service: (service != nil) ? (service!.id, service!.provider) : nil, + archived: archived, + archivedReference: archivedReference, + hidden: hidden, + hiddenReference: hiddenReference, + mutedStatus: mutedStatus, + mutedReference: mutedReference + ) + } + +} + +extension WireAPI.Conversation { + + func toDomainModel() -> WireDomain.Conversation { + .init( + id: id, + qualifiedID: qualifiedID?.toDomainModel(), + teamID: teamID, + type: type?.toDomainModel(), + messageProtocol: messageProtocol?.toDomainModel(), + mlsGroupID: mlsGroupID, + cipherSuite: cipherSuite?.toDomainModel(), + epoch: epoch, + epochTimestamp: epochTimestamp, + creator: creator, + members: members?.toDomainModel(), + name: name, + messageTimer: messageTimer, + readReceiptMode: readReceiptMode, + access: access?.map(\.rawValue), + accessRoles: accessRoles?.map(\.rawValue), + legacyAccessRole: legacyAccessRole?.toDomainModel(), + lastEvent: lastEvent, + lastEventTime: lastEventTime + ) + } + +} diff --git a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationRepository.swift b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationRepository.swift index c60e61fc6cb..0762dcb0056 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationRepository.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationRepository.swift @@ -19,6 +19,7 @@ import Foundation import WireAPI import WireDataModel +import WireLogging // sourcery: AutoMockable /// Facilitate access to conversations related domain objects. @@ -51,7 +52,7 @@ public protocol ConversationRepositoryProtocol { /// - timestamp: The date the conversation was created or last modified. func storeConversation( - _ conversation: WireAPI.Conversation, + _ conversation: WireDomain.Conversation, timestamp: Date ) async @@ -170,15 +171,40 @@ public protocol ConversationRepositoryProtocol { reason: ConversationMemberLeaveReason ) async throws - /// Adds a system message to a given conversation. - /// - parameters: - /// - message: The system message to add. - /// - conversation: The conversation to add the system message to. + /// Updates the conversation name locally. + /// - Parameters: + /// - newName: The new name for the conversation. + /// - conversationID: The conversation ID. + /// - conversationDomain: The conversation domain. + /// - senderID: The user ID. + /// - senderDomain: The user domain. + /// - date: The date the conversation name was updated. - func addSystemMessage( - _ message: SystemMessage, - to conversation: ZMConversation + func updateConversationName( + newName: String, + conversationID: UUID, + conversationDomain: String?, + senderID: UUID, + senderDomain: String?, + date: Date ) async + + /// Updates the typing users for a given conversation. + /// - Parameters: + /// - typingUsersInfo: A list of typing users for a given conversation. + + func updateTypingUsers( + _ typingUsersInfo: [ConversationTypingUsersInfo] + ) async + + /// Fetches the guest link for a given conversation. + /// - parameter conversationID: The conversation id. + /// - returns: The guest link. + + func fetchConversationGuestLink( + conversationID: String + ) async throws -> String? + } public final class ConversationRepository: ConversationRepositoryProtocol { @@ -186,6 +212,7 @@ public final class ConversationRepository: ConversationRepositoryProtocol { public struct BackendInfo { let domain: String let isFederationEnabled: Bool + let isMLSEnabled: Bool } // MARK: - Properties @@ -194,6 +221,7 @@ public final class ConversationRepository: ConversationRepositoryProtocol { private let conversationsLocalStore: any ConversationLocalStoreProtocol private let userRepository: any UserRepositoryProtocol private let teamRepository: any TeamRepositoryProtocol + private let messageRepository: any MessageRepositoryProtocol private let backendInfo: BackendInfo private let mlsProvider: MLSProvider @@ -204,6 +232,7 @@ public final class ConversationRepository: ConversationRepositoryProtocol { conversationsLocalStore: any ConversationLocalStoreProtocol, userRepository: any UserRepositoryProtocol, teamRepository: any TeamRepositoryProtocol, + messageRepository: any MessageRepositoryProtocol, backendInfo: BackendInfo, mlsProvider: MLSProvider ) { @@ -211,12 +240,28 @@ public final class ConversationRepository: ConversationRepositoryProtocol { self.conversationsLocalStore = conversationsLocalStore self.userRepository = userRepository self.teamRepository = teamRepository + self.messageRepository = messageRepository self.backendInfo = backendInfo self.mlsProvider = mlsProvider } // MARK: - Public + public func fetchConversationGuestLink( + conversationID: String + ) async throws -> String? { + + do { + return try await conversationsAPI.getConversationGuestLink( + conversationID: conversationID + ) + + } catch { + throw ConversationRepositoryError.failedToFetchGuestLink(error) + } + + } + public func pullConversation(id: UUID, domain: String) async throws { let qualifiedID = WireAPI.QualifiedID(uuid: id, domain: domain) let conversationList = try await conversationsAPI.getConversations( @@ -228,9 +273,10 @@ public final class ConversationRepository: ConversationRepositoryProtocol { } await conversationsLocalStore.storeConversation( - conversation, + conversation.toDomainModel(), timestamp: .now, - isFederationEnabled: backendInfo.isFederationEnabled + isFederationEnabled: backendInfo.isFederationEnabled, + isMLSEnabled: backendInfo.isMLSEnabled ) } @@ -255,25 +301,29 @@ public final class ConversationRepository: ConversationRepositoryProtocol { } public func storeConversation( - _ conversation: WireAPI.Conversation, + _ conversation: Conversation, timestamp: Date ) async { await conversationsLocalStore.storeConversation( conversation, timestamp: timestamp, - isFederationEnabled: backendInfo.isFederationEnabled + isFederationEnabled: backendInfo.isFederationEnabled, + isMLSEnabled: backendInfo.isMLSEnabled ) } public func pullConversations() async throws { var qualifiedIds: [WireAPI.QualifiedID] - if let result = try? await conversationsAPI - .getLegacyConversationIdentifiers() { // only for api v0 (see `ConversationsAPIV0` method comment) + if let result = + try? await conversationsAPI + .getLegacyConversationIdentifiers() { // only for api v0 (see `ConversationsAPIV0` method comment) let uuids = try await result.reduce(into: [UUID]()) { partialResult, uuids in partialResult.append(contentsOf: uuids) } - qualifiedIds = uuids.map { WireAPI.QualifiedID(uuid: $0, domain: backendInfo.domain) } + qualifiedIds = uuids.map { + WireAPI.QualifiedID(uuid: $0, domain: backendInfo.domain) + } } else { // fallback to api versions > v0. let ids = try await conversationsAPI.getConversationIdentifiers() @@ -282,7 +332,9 @@ public final class ConversationRepository: ConversationRepositoryProtocol { } } - let conversationList = try await conversationsAPI.getConversations(for: qualifiedIds) + let conversationList = try await conversationsAPI.getConversations( + for: qualifiedIds + ) await withThrowingTaskGroup(of: Void.self) { taskGroup in let foundConversations = conversationList.found @@ -292,7 +344,7 @@ public final class ConversationRepository: ConversationRepositoryProtocol { for conversation in foundConversations { taskGroup.addTask { [self] in await storeConversation( - conversation, + conversation.toDomainModel(), timestamp: .now ) } @@ -302,7 +354,8 @@ public final class ConversationRepository: ConversationRepositoryProtocol { taskGroup.addTask { [self] in await conversationsLocalStore.storeConversation( needsBackendUpdate: true, - qualifiedId: id + conversationID: id.uuid, + conversationDomain: id.domain ) } } @@ -310,7 +363,8 @@ public final class ConversationRepository: ConversationRepositoryProtocol { for id in failedConversationsQualifiedIds { taskGroup.addTask { [self] in await conversationsLocalStore.storeFailedConversation( - withQualifiedId: id + conversationID: id.uuid, + conversationDomain: id.domain ) } } @@ -321,19 +375,21 @@ public final class ConversationRepository: ConversationRepositoryProtocol { userID: String, userDomain: String ) async throws -> String { - let mlsConversation = try await conversationsAPI.getMLSOneToOneConversation( - userID: userID, - in: userDomain - ) + let mlsConversation = + try await conversationsAPI.getMLSOneToOneConversation( + userID: userID, + in: userDomain + ) guard let mlsGroupID = mlsConversation.mlsGroupID else { throw ConversationRepositoryError.mlsConversationShouldHaveAGroupID } await conversationsLocalStore.storeConversation( - mlsConversation, + mlsConversation.toDomainModel(), timestamp: .now, - isFederationEnabled: backendInfo.isFederationEnabled + isFederationEnabled: backendInfo.isFederationEnabled, + isMLSEnabled: backendInfo.isMLSEnabled ) return mlsGroupID @@ -356,25 +412,61 @@ public final class ConversationRepository: ConversationRepositoryProtocol { participantDomain: String?, removedAt date: Date ) async throws { - let user = try await userRepository.fetchUser( - id: participantID, - domain: participantDomain - ) - await conversationsLocalStore.removeParticipantFromAllGroupConversations( - user: user, + try await conversationsLocalStore.removeParticipantFromAllGroupConversations( + participantID: participantID, + participantDomain: participantDomain, date: date ) } + public func updateConversationName( + newName: String, + conversationID: UUID, + conversationDomain: String?, + senderID: UUID, + senderDomain: String?, + date: Date + ) async { + + let conversation = await fetchOrCreateConversation( + id: conversationID, + domain: conversationDomain + ) + + let currentConversationName = await conversationsLocalStore.conversationName(conversation: conversation) + + if currentConversationName != newName { + let messageType = MessageType.conversationNameChanged( + newName: newName, + sender: (senderID, senderDomain), + date: date + ) + + await messageRepository.addMessageToConversation( + messageType: messageType, + conversationID: conversationID, + conversationDomain: conversationDomain + ) + } + + await conversationsLocalStore.storeConversation( + newName: newName, + conversation: conversation + ) + + } + public func deleteConversation( id: UUID, domain: String? ) async throws { - guard let conversation = await fetchConversation( - id: id, - domain: domain - ) else { + guard + let conversation = await fetchConversation( + id: id, + domain: domain + ) + else { return WireLogger.conversation.warn( "Cannot delete a conversation that doesn't exist locally: \(id.safeForLoggingDescription)" ) @@ -390,7 +482,9 @@ public final class ConversationRepository: ConversationRepositoryProtocol { ) if let mlsGroupID { - try await conversationsLocalStore.wipeMLSGroup(groupID: mlsGroupID) + try await conversationsLocalStore.wipeMLSGroup( + groupID: mlsGroupID + ) } await conversationsLocalStore.deleteConversation( @@ -435,7 +529,7 @@ public final class ConversationRepository: ConversationRepositoryProtocol { conversationID: UUID, conversationDomain: String ) async throws { - var conversation = await fetchConversation( + let conversation = await fetchConversation( id: conversationID, domain: conversationDomain ) @@ -446,27 +540,13 @@ public final class ConversationRepository: ConversationRepositoryProtocol { id: conversationID, domain: conversationDomain ) - - conversation = await fetchConversation( - id: conversationID, - domain: conversationDomain - ) - } - - guard let conversation else { - return WireLogger.eventProcessing.error( - "Member join update missing conversation, aborting... ", - attributes: [ - .conversationId: conversationID.safeForLoggingDescription - ] - ) } try await conversationsLocalStore.addParticipants( participants, addedBy: sender, atDate: date, - to: conversation + conversation: (conversationID, conversationDomain) ) } @@ -474,45 +554,55 @@ public final class ConversationRepository: ConversationRepositoryProtocol { _ userIDs: Set, from conversation: ConversationID, initiatedBy sender: UserID, - at time: Date, + at date: Date, reason: ConversationMemberLeaveReason ) async throws { - let id = conversation.uuid - let domain = conversation.domain + let conversationID = conversation.uuid + let conversationDomain = conversation.domain + let senderID = sender.uuid + let senderDomain = sender.domain let removedUserIDs = userIDs - let conversation = await conversationsLocalStore.fetchOrCreateConversation( - id: id, - domain: domain - ) + let conversation = + await conversationsLocalStore.fetchOrCreateConversation( + id: conversationID, + domain: conversationDomain + ) let removedUsers = await getRemovedUsers(from: removedUserIDs) - let participants = await conversationsLocalStore.localParticipants(in: conversation) + let participants = await conversationsLocalStore.localParticipants( + in: conversation + ) let sender = try await userRepository.fetchUser( - id: sender.uuid, - domain: sender.domain + id: senderID, + domain: senderDomain ) if !participants.isDisjoint(with: removedUsers) { - let systemMessage = SystemMessage( - type: reason.toDomainModel(), - sender: sender, - timestamp: time + await addSystemMessage( + conversationID: conversationID, + conversationDomain: conversationDomain, + senderID: senderID, + senderDomain: senderDomain, + date: date, + removedUsers: removedUserIDs, + reason: reason ) - - await addSystemMessage(systemMessage, to: conversation) } let isSelfUserRemoved = await isSelfUserRemoved(in: removedUserIDs) - let messageProtocol = await conversationsLocalStore.messageProtocol(for: conversation) - - await conversationsLocalStore.removeParticipantsAndUpdateConversationState( - conversation: conversation, - users: Set(removedUsers), - initiatingUser: sender + let messageProtocol = await conversationsLocalStore.messageProtocol( + for: conversation ) + await conversationsLocalStore + .removeParticipantsAndUpdateConversationState( + conversation: conversation, + users: Set(removedUsers), + initiatingUser: sender + ) + let isMLSEnabled = mlsProvider.isMLSEnabled let mlsService = mlsProvider.service @@ -521,7 +611,8 @@ public final class ConversationRepository: ConversationRepositoryProtocol { for: conversation ) - if isSelfUserRemoved, let mlsGroupID, messageProtocol.isOne(of: .mls, .mixed) { + if isSelfUserRemoved, let mlsGroupID, + messageProtocol.isOne(of: .mls, .mixed) { try await mlsService.wipeGroup(mlsGroupID) } } @@ -530,21 +621,52 @@ public final class ConversationRepository: ConversationRepositoryProtocol { return } - await deleteMembership(for: removedUserIDs, time: time) + await deleteMembership(for: removedUserIDs, time: date) } - public func addSystemMessage( - _ message: SystemMessage, - to conversation: ZMConversation + public func updateTypingUsers( + _ typingUsersInfo: [ConversationTypingUsersInfo] ) async { - await conversationsLocalStore.addSystemMessage( - message, - to: conversation - ) + for typingUserInfo in typingUsersInfo { + await conversationsLocalStore.updateTypingUsers( + conversationID: typingUserInfo.conversationID, + usersID: typingUserInfo.users + ) + } } // MARK: - Private + private func addSystemMessage( + conversationID: UUID, + conversationDomain: String?, + senderID: UUID, + senderDomain: String?, + date: Date, + removedUsers: Set, + reason: ConversationMemberLeaveReason + ) async { + var systemMessageType: MessageType = switch reason { + case .userDeleted, .userLeft: + .teamMemberRemoved( + member: (senderID, senderDomain), + date: date + ) + case .userRemoved: + .participantsRemoved( + participants: removedUsers.map { ($0.uuid, $0.domain) }, + sender: (senderID, senderDomain), + date: date + ) + } + + await messageRepository.addMessageToConversation( + messageType: systemMessageType, + conversationID: conversationID, + conversationDomain: conversationDomain + ) + } + private func getRemovedUsers( from userIDs: Set ) async -> [WireDataModel.ZMUser] { @@ -599,9 +721,9 @@ public final class ConversationRepository: ConversationRepositoryProtocol { taskGroup.addTask { [self] in do { try await teamRepository.deleteMembership( - for: userID.uuid, + userID: userID.uuid, domain: userID.domain, - at: time + date: time ) } catch { WireLogger.eventProcessing.error( diff --git a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationRepositoryError.swift b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationRepositoryError.swift index 2b5bc22653e..a38d05cc9e0 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationRepositoryError.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Conversations/ConversationRepositoryError.swift @@ -34,4 +34,8 @@ enum ConversationRepositoryError: Error { case mlsConversationShouldHaveAGroupID + /// Unable to fetch conversation guest link + + case failedToFetchGuestLink(Error) + } diff --git a/WireDomain/Sources/WireDomain/Repositories/Conversations/Models/Conversation.swift b/WireDomain/Sources/WireDomain/Repositories/Conversations/Models/Conversation.swift new file mode 100644 index 00000000000..c14654eb77d --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/Conversations/Models/Conversation.swift @@ -0,0 +1,63 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel + +public struct Conversation { + + struct Members { + let others: [Member] + let selfMember: Member + + struct Member { + let qualifiedID: QualifiedID? + let id: UUID? + let qualifiedTarget: QualifiedID? + let target: UUID? + let conversationRole: String? + let service: (id: UUID, provider: UUID)? + let archived: Bool? + let archivedReference: Date? + let hidden: Bool? + let hiddenReference: String? + let mutedStatus: Int? + let mutedReference: Date? + } + } + + let id: UUID? + let qualifiedID: QualifiedID? + let teamID: UUID? + let type: BackendConversationType? + let messageProtocol: MessageProtocol? + let mlsGroupID: String? + let cipherSuite: MLSCipherSuite? + let epoch: UInt? + let epochTimestamp: Date? + let creator: UUID? + let members: Members? + let name: String? + let messageTimer: TimeInterval? + let readReceiptMode: Int? + let access: [String]? + let accessRoles: [String]? + let legacyAccessRole: ConversationAccessRole? + let lastEvent: String? + let lastEventTime: Date? + +} diff --git a/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsLocalStore.swift new file mode 100644 index 00000000000..4ea209ff051 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsLocalStore.swift @@ -0,0 +1,160 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel +import WireLogging + +// sourcery: AutoMockable +public protocol ConversationLabelsLocalStoreProtocol { + + /// Save label and related conversations objects to local storage. + /// - Parameter conversationLabel: conversation label info + + func storeLabel( + _ conversationLabel: ConversationLabelInfo + ) async throws + + /// Delete old `folder` labels and related conversations objects from local storage. + /// - Parameter excludedLabels: remote labels that should be excluded from deletion. + /// - Only old labels of type `folder` are deleted, `favorite` labels always remain in the local storage. + + func deleteOldLabelsLocally( + excludedLabels: [ConversationLabelInfo] + ) async throws +} + +public final class ConversationLabelsLocalStore: ConversationLabelsLocalStoreProtocol { + + // MARK: - Error + + enum Failure: Error { + case failedToStoreLabelLocally(UUID) + } + + // MARK: - Properties + + private let context: NSManagedObjectContext + private let logger = WireLogger(tag: "conversation-labels") + + // MARK: - Object lifecycle + + init( + context: NSManagedObjectContext + ) { + self.context = context + } + + // MARK: - Public + + /// Save label and related conversations objects to local storage. + /// - Parameter conversationLabel: conversation label + + public func storeLabel( + _ conversationLabel: ConversationLabelInfo + ) async throws { + try await context.perform { [context] in + var created = false + let label: Label? = if conversationLabel.type == Label.Kind.favorite.rawValue { + Label.fetchFavoriteLabel(in: context) + } else { + Label.fetchOrCreate( + remoteIdentifier: conversationLabel.id, + create: true, + in: context, + created: &created + ) + } + + guard let label else { + throw Failure.failedToStoreLabelLocally(conversationLabel.id) + } + + label.name = conversationLabel.name + label.kind = Label.Kind(rawValue: conversationLabel.type) ?? .folder + + let conversations = ZMConversation.fetchObjects( + withRemoteIdentifiers: Set(conversationLabel.conversationIDs), + in: context + ) as? Set ?? Set() + + label.conversations = conversations + label.modifiedKeys = nil + + do { + try context.save() + } catch { + throw Failure.failedToStoreLabelLocally(conversationLabel.id) + } + } + } + + public func deleteOldLabelsLocally( + excludedLabels: [ConversationLabelInfo] + ) async throws { + try await context.perform { [self] in + let uuids = excludedLabels.map { $0.id.uuidData as NSData } + let predicateFormat = "type == \(Label.Kind.folder.rawValue) AND NOT remoteIdentifier_data IN %@" + + let predicate = NSPredicate( + format: predicateFormat, + uuids as CVarArg + ) + + let fetchRequest: NSFetchRequest + fetchRequest = NSFetchRequest(entityName: Label.entityName()) + fetchRequest.predicate = predicate + + /// Since batch operations bypass the context processing, + /// relationships rules are often ignored (e.g delete rule) + /// Nevertheless, CoreData automatically handles two specific scenarios: + /// `Cascade` delete rule and `Nullify` delete rule on an optional property + /// Since `conversations` is nullify and optional, we can safely perform a batch delete. + + let deleteRequest = NSBatchDeleteRequest( + fetchRequest: fetchRequest + ) + + deleteRequest.resultType = .resultTypeObjectIDs + + do { + let batchDelete = try context.execute(deleteRequest) as? NSBatchDeleteResult + + guard let deleteResult = batchDelete?.result as? [NSManagedObjectID] else { + throw ConversationLabelsRepositoryError.failedToDeleteStoredLabels + } + + let deletedObjects: [AnyHashable: Any] = [ + NSDeletedObjectsKey: deleteResult + ] + + /// Since `NSBatchDeleteRequest` only operates at the SQL level (in the persistent store itself), + /// we need to manually update our in-memory objects after execution. + + NSManagedObjectContext.mergeChanges( + fromRemoteContextSave: deletedObjects, + into: [context] + ) + + } catch { + logger.error("Failed to delete old labels: \(error)") + throw error + } + } + } + +} diff --git a/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsModelMappings.swift b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsModelMappings.swift new file mode 100644 index 00000000000..de1c5bf7783 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsModelMappings.swift @@ -0,0 +1,31 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireAPI + +extension WireAPI.ConversationLabel { + + func toDomainModel() -> ConversationLabelInfo { + .init( + id: id, + name: name, + type: type, + conversationIDs: conversationIDs + ) + } +} diff --git a/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepository.swift b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepository.swift index d2fce7943ae..f5045897c2e 100644 --- a/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepository.swift +++ b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepository.swift @@ -16,9 +16,9 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import CoreData import WireAPI import WireDataModel +import WireLogging // sourcery: AutoMockable /// Facilitates access to conversation labels related domain objects. @@ -42,25 +42,21 @@ public class ConversationLabelsRepository: ConversationLabelsRepositoryProtocol // MARK: - Properties private let userPropertiesAPI: any UserPropertiesAPI - // swiftlint:disable:next todo_requires_jira_link - // TODO: create ConversationLabelsLocalStore - private let context: NSManagedObjectContext + private let conversationLabelsLocalStore: any ConversationLabelsLocalStoreProtocol private let logger = WireLogger(tag: "conversation-labels") // MARK: - Object lifecycle init( userPropertiesAPI: any UserPropertiesAPI, - context: NSManagedObjectContext + conversationLabelsLocalStore: any ConversationLabelsLocalStoreProtocol ) { self.userPropertiesAPI = userPropertiesAPI - self.context = context + self.conversationLabelsLocalStore = conversationLabelsLocalStore } // MARK: - Public - /// Retrieve from backend and store conversation labels locally - public func pullConversationLabels() async throws { let conversationLabels = try await userPropertiesAPI.getLabels() try await updateConversationLabels(conversationLabels) @@ -70,7 +66,10 @@ public class ConversationLabelsRepository: ConversationLabelsRepositoryProtocol _ conversationLabels: [ConversationLabel] ) async throws { await storeLabelsLocally(conversationLabels) - try await deleteOldLabelsLocally(excludedLabels: conversationLabels) + + try await conversationLabelsLocalStore.deleteOldLabelsLocally( + excludedLabels: conversationLabels.map { $0.toDomainModel() } + ) } // MARK: - Private @@ -81,7 +80,9 @@ public class ConversationLabelsRepository: ConversationLabelsRepositoryProtocol await withThrowingTaskGroup(of: Void.self) { taskGroup in for conversationLabel in conversationLabels { taskGroup.addTask { [self] in - try await storeLabelLocally(conversationLabel) + try await conversationLabelsLocalStore.storeLabel( + conversationLabel.toDomainModel() + ) } } @@ -91,9 +92,12 @@ public class ConversationLabelsRepository: ConversationLabelsRepositoryProtocol case .success: continue case let .failure(error): - let repoError = error as? ConversationLabelsRepositoryError - if case let .failedToStoreLabelLocally(label) = repoError { - logger.error("Failed to store conversation label with id \(label.id): \(error)") + let repoError = error as? ConversationLabelsLocalStore.Failure + if case let .failedToStoreLabelLocally(id) = repoError { + logger + .error( + "Failed to store conversation label with id \(id.safeForLoggingDescription): \(error)" + ) } else { logger.error("Failed to store conversation with error: \(error)") } @@ -101,105 +105,4 @@ public class ConversationLabelsRepository: ConversationLabelsRepositoryProtocol } } } - - /// Save label and related conversations objects to local storage. - /// - Parameter conversationLabel: conversation label from WireAPI - - private func storeLabelLocally( - _ conversationLabel: ConversationLabel - ) async throws { - try await context.perform { [context] in - var created = false - let label: Label? = if conversationLabel.type == Label.Kind.favorite.rawValue { - Label.fetchFavoriteLabel(in: context) - } else { - Label.fetchOrCreate( - remoteIdentifier: conversationLabel.id, - create: true, - in: context, - created: &created - ) - } - - guard let label else { - throw ConversationLabelsRepositoryError.failedToStoreLabelLocally(conversationLabel) - } - - label.name = conversationLabel.name - label.kind = Label.Kind(rawValue: conversationLabel.type) ?? .folder - - let conversations = ZMConversation.fetchObjects( - withRemoteIdentifiers: Set(conversationLabel.conversationIDs), - in: context - ) as? Set ?? Set() - - label.conversations = conversations - label.modifiedKeys = nil - - do { - try context.save() - } catch { - throw ConversationLabelsRepositoryError.failedToStoreLabelLocally(conversationLabel) - } - } - } - - /// Delete old `folder` labels and related conversations objects from local storage. - /// - Parameter excludedLabels: remote labels that should be excluded from deletion. - /// - Only old labels of type `folder` are deleted, `favorite` labels always remain in the local storage. - - private func deleteOldLabelsLocally( - excludedLabels remoteLabels: [ConversationLabel] - ) async throws { - try await context.perform { [self] in - let uuids = remoteLabels.map { $0.id.uuidData as NSData } - let predicateFormat = "type == \(Label.Kind.folder.rawValue) AND NOT remoteIdentifier_data IN %@" - - let predicate = NSPredicate( - format: predicateFormat, - uuids as CVarArg - ) - - let fetchRequest: NSFetchRequest - fetchRequest = NSFetchRequest(entityName: Label.entityName()) - fetchRequest.predicate = predicate - - /// Since batch operations bypass the context processing, - /// relationships rules are often ignored (e.g delete rule) - /// Nevertheless, CoreData automatically handles two specific scenarios: - /// `Cascade` delete rule and `Nullify` delete rule on an optional property - /// Since `conversations` is nullify and optional, we can safely perform a batch delete. - - let deleteRequest = NSBatchDeleteRequest( - fetchRequest: fetchRequest - ) - - deleteRequest.resultType = .resultTypeObjectIDs - - do { - let batchDelete = try context.execute(deleteRequest) as? NSBatchDeleteResult - - guard let deleteResult = batchDelete?.result as? [NSManagedObjectID] else { - throw ConversationLabelsRepositoryError.failedToDeleteStoredLabels - } - - let deletedObjects: [AnyHashable: Any] = [ - NSDeletedObjectsKey: deleteResult - ] - - /// Since `NSBatchDeleteRequest` only operates at the SQL level (in the persistent store itself), - /// we need to manually update our in-memory objects after execution. - - NSManagedObjectContext.mergeChanges( - fromRemoteContextSave: deletedObjects, - into: [context] - ) - - } catch { - logger.error("Failed to delete old labels: \(error)") - throw error - } - } - } - } diff --git a/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepositoryError.swift b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepositoryError.swift index d5c17089b81..fa999e54fb7 100644 --- a/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepositoryError.swift +++ b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepositoryError.swift @@ -16,16 +16,13 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import Foundation import WireAPI /// Errors originating from `ConversationLabelsRepository`. enum ConversationLabelsRepositoryError: Error, Equatable { - /// Unable to store label locally - - case failedToStoreLabelLocally(ConversationLabel) - /// Unable to pull labels from backend case failedToCollectLabelsRemotely diff --git a/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/Models/ConversationLabelInfo.swift b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/Models/ConversationLabelInfo.swift new file mode 100644 index 00000000000..75c956c98ed --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/Models/ConversationLabelInfo.swift @@ -0,0 +1,26 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +public struct ConversationLabelInfo: Sendable { + let id: UUID + let name: String? + let type: Int16 + let conversationIDs: [UUID] +} diff --git a/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/FeatureConfigLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/FeatureConfigLocalStore.swift new file mode 100644 index 00000000000..e01a1aaf605 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/FeatureConfigLocalStore.swift @@ -0,0 +1,171 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel +import WireLogging + +protocol FeatureConfigLocalStoreProtocol { + + /// Fetches a feature locally. + /// - parameter name: The name of the feature. + /// - returns: The feature found locally + + func fetchFeature( + name: Feature.Name + ) async throws -> Feature + + /// Stores a flag indicating whether the user needs to be notified of the feature. + /// - parameters: + /// - needsNotifyUser: The flag to update. + /// - feature: The feature to update the flag for. + + func storeFeature( + needsNotifyUser: Bool, + feature: Feature + ) async + + /// Fetches a flag whether the user needs to be notified for the feature. + /// - parameter feature: A given feature. + /// - returns: Whether the user needs to be notified. + + func featureNeedsNotifyUser( + feature: Feature + ) async -> Bool + + /// Stores a feature locally. + /// - parameters: + /// - name: The name of the feature + /// - isEnabled: Whether the feature is enabled. + /// - config: The config of the feature if any. + + func storeFeature( + name: Feature.Name, + isEnabled: Bool, + config: (any Codable)? + ) async + + /// Fetches a feature config info. + /// - parameter feature: The feature to fetch the info from. + /// - returns: A status (enabled or disabled) and a config payload. + + func featureConfig( + feature: Feature + ) async -> (status: Feature.Status, config: Data?) + +} + +final class FeatureConfigLocalStore: FeatureConfigLocalStoreProtocol { + + // MARK: - Error + + enum Error: Swift.Error { + case failedToFetchFeatureLocally + } + + // MARK: - Properties + + private let context: NSManagedObjectContext + + // MARK: - Object lifecycle + + init( + context: NSManagedObjectContext + ) { + self.context = context + } + + // MARK: - Public + + public func fetchFeature( + name: Feature.Name + ) async throws -> Feature { + try await context.perform { [context] in + guard let feature = Feature.fetch( + name: name, + context: context + ) else { + throw Error.failedToFetchFeatureLocally + } + + return feature + } + } + + public func storeFeature( + needsNotifyUser: Bool, + feature: Feature + ) async { + await context.perform { + feature.needsToNotifyUser = needsNotifyUser + } + } + + public func featureConfig( + feature: Feature + ) async -> (status: Feature.Status, config: Data?) { + await context.perform { + (feature.status, feature.config) + } + } + + func featureNeedsNotifyUser( + feature: Feature + ) async -> Bool { + await context.perform { + feature.needsToNotifyUser + } + } + + public func storeFeature( + name: Feature.Name, + isEnabled: Bool, + config: (any Codable)? = nil + ) async { + await context.perform { [context] in + if let config { + let encoder = JSONEncoder() + + do { + let data = try encoder.encode(config) + + Feature.updateOrCreate( + havingName: name, + in: context + ) { + $0.status = isEnabled ? .enabled : .disabled + $0.config = data + } + + } catch { + WireLogger.featureConfigs.error( + "Failed to encode \(String(describing: config.self)) : \(error)" + ) + } + + } else { + Feature.updateOrCreate( + havingName: name, + in: context + ) { + $0.status = isEnabled ? .enabled : .disabled + } + } + } + } + +} diff --git a/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/FeatureConfigRepository.swift b/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/FeatureConfigRepository.swift index 2124c9dae00..ed163b10591 100644 --- a/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/FeatureConfigRepository.swift +++ b/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/FeatureConfigRepository.swift @@ -16,11 +16,10 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - import Combine import WireAPI import WireDataModel +import WireLogging /// Facilitates access to feature configs related domain objects. protocol FeatureConfigRepositoryProtocol { @@ -48,25 +47,35 @@ protocol FeatureConfigRepositoryProtocol { /// - Parameter type: The type of config to retrieve. /// - Returns: A `LocalFeature` object with a status and a config (if any). - func fetchFeatureConfig(with name: Feature.Name, type: T.Type) async throws -> LocalFeature + func fetchFeatureConfig( + name: Feature.Name, + type: T.Type + ) async throws -> LocalFeature /// Updates a feature config locally. /// /// - Parameter featureConfig: The feature config to update. - func updateFeatureConfig(_ featureConfig: FeatureConfig) async throws + func updateFeatureConfig( + _ featureConfig: FeatureConfig + ) async throws /// Fetches a flag indicating whether the user should be notified of a given feature. /// - Parameter name: The feature name. /// - Returns: `true` if user should be notified. - func fetchNeedsToNotifyUser(for name: Feature.Name) async throws -> Bool + func needsToNotifyUser( + name: Feature.Name + ) async throws -> Bool /// Stores a flag indicating whether the user should be notified of a given feature. /// - Parameter notifyUser: Whether the user should be notified for a given feature. /// - Parameter name: The name of the feature to set the flag for. - func storeNeedsToNotifyUser(_ notifyUser: Bool, forFeatureName name: Feature.Name) async throws + func storeFeatureNeedsToNotifyUser( + _ notifyUser: Bool, + name: Feature.Name + ) async throws } @@ -75,9 +84,7 @@ final class FeatureConfigRepository: FeatureConfigRepositoryProtocol { // MARK: - Properties private let featureConfigsAPI: any FeatureConfigsAPI - // swiftlint:disable:next todo_requires_jira_link - // TODO: create FeatureConfigLocalStore - private let context: NSManagedObjectContext + private let featureConfigLocalStore: any FeatureConfigLocalStoreProtocol private let logger = WireLogger.featureConfigs private let featureStateSubject = PassthroughSubject() @@ -85,10 +92,10 @@ final class FeatureConfigRepository: FeatureConfigRepositoryProtocol { init( featureConfigsAPI: any FeatureConfigsAPI, - context: NSManagedObjectContext + featureConfigLocalStore: any FeatureConfigLocalStoreProtocol ) { self.featureConfigsAPI = featureConfigsAPI - self.context = context + self.featureConfigLocalStore = featureConfigLocalStore } // MARK: - Public @@ -97,8 +104,15 @@ final class FeatureConfigRepository: FeatureConfigRepositoryProtocol { let featureConfigs = try await featureConfigsAPI.getFeatureConfigs() for featureConfig in featureConfigs { - await storeFeatureConfig(featureConfig) - await sendFeatureState(for: featureConfig) + if let featureConfigInfo = getFeatureConfigInfo(featureConfig) { + await featureConfigLocalStore.storeFeature( + name: featureConfigInfo.name, + isEnabled: featureConfigInfo.isEnabled, + config: featureConfigInfo.config + ) + + await sendFeatureState(for: featureConfig) + } } } @@ -106,37 +120,67 @@ final class FeatureConfigRepository: FeatureConfigRepositoryProtocol { featureStateSubject.eraseToAnyPublisher() } - func fetchFeatureConfig(with name: Feature.Name, type: T.Type) async throws -> LocalFeature { - try await context.perform { [self] in - let feature = try fetchFeature(withName: name) + func fetchFeatureConfig( + name: Feature.Name, + type: T.Type + ) async throws -> LocalFeature { + let feature = try await featureConfigLocalStore.fetchFeature( + name: name + ) - if let config = feature.config { - let decoder = JSONDecoder() - let config = try decoder.decode(type, from: config) + let featureConfig = await featureConfigLocalStore.featureConfig(feature: feature) - return LocalFeature(status: feature.status, config: config) - } + if let config = featureConfig.config { + let decoder = JSONDecoder() + let config = try decoder.decode(type, from: config) - return LocalFeature(status: feature.status, config: nil) + return LocalFeature(status: featureConfig.status, config: config) } + + return LocalFeature(status: featureConfig.status, config: nil) } - func fetchNeedsToNotifyUser(for name: Feature.Name) async throws -> Bool { - try await context.perform { [self] in - let feature = try fetchFeature(withName: name) - return feature.needsToNotifyUser - } + func needsToNotifyUser( + name: Feature.Name + ) async throws -> Bool { + let feature = try await featureConfigLocalStore.fetchFeature( + name: name + ) + + return await featureConfigLocalStore.featureNeedsNotifyUser( + feature: feature + ) } - func storeNeedsToNotifyUser(_ notifyUser: Bool, forFeatureName name: Feature.Name) async throws { - try await context.perform { [self] in - let feature = try fetchFeature(withName: name) - feature.needsToNotifyUser = notifyUser - } + func storeFeatureNeedsToNotifyUser( + _ notifyUser: Bool, + name: Feature.Name + ) async throws { + let feature = try await featureConfigLocalStore.fetchFeature( + name: name + ) + + await featureConfigLocalStore.storeFeature( + needsNotifyUser: notifyUser, + feature: feature + ) } - func updateFeatureConfig(_ featureConfig: FeatureConfig) async throws { - await storeFeatureConfig(featureConfig) + func updateFeatureConfig( + _ featureConfig: FeatureConfig + ) async throws { + guard let featureConfigInfo = getFeatureConfigInfo( + featureConfig + ) else { + return + } + + await featureConfigLocalStore.storeFeature( + name: featureConfigInfo.name, + isEnabled: featureConfigInfo.isEnabled, + config: featureConfigInfo.config + ) + await sendFeatureState(for: featureConfig) } @@ -150,21 +194,13 @@ final class FeatureConfigRepository: FeatureConfigRepositoryProtocol { featureStateSubject.send(featureState) } - private func fetchFeature(withName name: Feature.Name) throws -> Feature { - guard let feature = Feature.fetch(name: name, context: context) else { - throw FeatureConfigRepositoryError.failedToFetchFeatureLocally - } - - return feature - } - private func getFeatureState(forFeatureConfig config: FeatureConfig) async throws -> FeatureState? { switch config { case let .appLock(appLockFeatureConfig): return FeatureState( name: .appLock, - status: appLockFeatureConfig.status, + isEnabled: appLockFeatureConfig.status == .enabled, shouldNotifyUser: false ) @@ -172,25 +208,25 @@ final class FeatureConfigRepository: FeatureConfigRepositoryProtocol { return FeatureState( name: .classifiedDomains, - status: classifiedDomainsFeatureConfig.status, + isEnabled: classifiedDomainsFeatureConfig.status == .enabled, shouldNotifyUser: false ) case let .conferenceCalling(conferenceCallingFeatureConfig): - let needsToNotifyUser = try await fetchNeedsToNotifyUser(for: .conferenceCalling) + let needsToNotifyUser = try await needsToNotifyUser(name: .conferenceCalling) return FeatureState( name: .conferenceCalling, - status: conferenceCallingFeatureConfig.status, + isEnabled: conferenceCallingFeatureConfig.status == .enabled, shouldNotifyUser: needsToNotifyUser ) case let .conversationGuestLinks(conversationGuestLinksFeatureConfig): - let needsToNotifyUser = try await fetchNeedsToNotifyUser(for: .conversationGuestLinks) + let needsToNotifyUser = try await needsToNotifyUser(name: .conversationGuestLinks) return FeatureState( name: .conversationGuestLinks, - status: conversationGuestLinksFeatureConfig.status, + isEnabled: conversationGuestLinksFeatureConfig.status == .enabled, shouldNotifyUser: needsToNotifyUser ) @@ -198,7 +234,7 @@ final class FeatureConfigRepository: FeatureConfigRepositoryProtocol { return FeatureState( name: .digitalSignature, - status: digitalSignatureFeatureConfig.status, + isEnabled: digitalSignatureFeatureConfig.status == .enabled, shouldNotifyUser: false ) @@ -206,16 +242,16 @@ final class FeatureConfigRepository: FeatureConfigRepositoryProtocol { return FeatureState( name: .e2ei, - status: endToEndIdentityFeatureConfig.status, + isEnabled: endToEndIdentityFeatureConfig.status == .enabled, shouldNotifyUser: false ) case let .fileSharing(fileSharingFeatureConfig): - let needsToNotifyUser = try await fetchNeedsToNotifyUser(for: .fileSharing) + let needsToNotifyUser = try await needsToNotifyUser(name: .fileSharing) return FeatureState( name: .fileSharing, - status: fileSharingFeatureConfig.status, + isEnabled: fileSharingFeatureConfig.status == .enabled, shouldNotifyUser: needsToNotifyUser ) @@ -223,7 +259,7 @@ final class FeatureConfigRepository: FeatureConfigRepositoryProtocol { return FeatureState( name: .mls, - status: mlsFeatureConfig.status, + isEnabled: mlsFeatureConfig.status == .enabled, shouldNotifyUser: false ) @@ -231,16 +267,16 @@ final class FeatureConfigRepository: FeatureConfigRepositoryProtocol { return FeatureState( name: .mlsMigration, - status: mLSMigrationFeatureConfig.status, + isEnabled: mLSMigrationFeatureConfig.status == .enabled, shouldNotifyUser: false ) case let .selfDeletingMessages(selfDeletingMessagesFeatureConfig): - let needsToNotifyUser = try await fetchNeedsToNotifyUser(for: .selfDeletingMessages) + let needsToNotifyUser = try await needsToNotifyUser(name: .selfDeletingMessages) return FeatureState( name: .selfDeletingMessages, - status: selfDeletingMessagesFeatureConfig.status, + isEnabled: selfDeletingMessagesFeatureConfig.status == .enabled, shouldNotifyUser: needsToNotifyUser ) @@ -253,144 +289,97 @@ final class FeatureConfigRepository: FeatureConfigRepositoryProtocol { } } - private func storeFeatureConfig(_ featureConfig: FeatureConfig) async { - await context.perform { [self] in - - switch featureConfig { - case let .appLock(appLockFeatureConfig): - - updateOrCreate( - featureName: .appLock, - isEnabled: appLockFeatureConfig.status == .enabled, - config: appLockFeatureConfig.toDomainModel() - ) - - case let .classifiedDomains(classifiedDomainsFeatureConfig): + private func getFeatureConfigInfo( + _ featureConfig: FeatureConfig + ) -> (name: Feature.Name, isEnabled: Bool, config: (any Codable)?)? { + switch featureConfig { + case let .appLock(appLockFeatureConfig): - updateOrCreate( - featureName: .classifiedDomains, - isEnabled: classifiedDomainsFeatureConfig.status == .enabled, - config: classifiedDomainsFeatureConfig.toDomainModel() - ) + return ( + .appLock, + appLockFeatureConfig.status == .enabled, + appLockFeatureConfig.toDomainModel() + ) - case let .conferenceCalling(conferenceCallingFeatureConfig): + case let .classifiedDomains(classifiedDomainsFeatureConfig): - updateOrCreate( - featureName: .conferenceCalling, - isEnabled: conferenceCallingFeatureConfig.status == .enabled, - config: conferenceCallingFeatureConfig.toDomainModel() /// always nil for api < v6 - ) + return ( + .classifiedDomains, + classifiedDomainsFeatureConfig.status == .enabled, + classifiedDomainsFeatureConfig.toDomainModel() + ) - case let .conversationGuestLinks(conversationGuestLinksFeatureConfig): + case let .conferenceCalling(conferenceCallingFeatureConfig): - updateOrCreate( - featureName: .conversationGuestLinks, - isEnabled: conversationGuestLinksFeatureConfig.status == .enabled - ) + return ( + .conferenceCalling, + conferenceCallingFeatureConfig.status == .enabled, + conferenceCallingFeatureConfig.toDomainModel() + ) - case let .digitalSignature(digitalSignatureFeatureConfig): + case let .conversationGuestLinks(conversationGuestLinksFeatureConfig): - updateOrCreate( - featureName: .digitalSignature, - isEnabled: digitalSignatureFeatureConfig.status == .enabled - ) + return ( + .conversationGuestLinks, + conversationGuestLinksFeatureConfig.status == .enabled, + nil + ) - case let .endToEndIdentity(endToEndIdentityFeatureConfig): + case let .digitalSignature(digitalSignatureFeatureConfig): - updateOrCreate( - featureName: .e2ei, - isEnabled: endToEndIdentityFeatureConfig.status == .enabled, - config: endToEndIdentityFeatureConfig.toDomainModel() - ) + return ( + .digitalSignature, + digitalSignatureFeatureConfig.status == .enabled, + nil + ) - case let .fileSharing(fileSharingFeatureConfig): + case let .endToEndIdentity(endToEndIdentityFeatureConfig): - updateOrCreate( - featureName: .fileSharing, - isEnabled: fileSharingFeatureConfig.status == .enabled - ) + return ( + .e2ei, + endToEndIdentityFeatureConfig.status == .enabled, + nil + ) - case let .mls(mlsFeatureConfig): + case let .fileSharing(fileSharingFeatureConfig): - updateOrCreate( - featureName: .mls, - isEnabled: mlsFeatureConfig.status == .enabled, - config: mlsFeatureConfig.toDomainModel() - ) + return ( + .fileSharing, + fileSharingFeatureConfig.status == .enabled, + nil + ) - case let .mlsMigration(mLSMigrationFeatureConfig): + case let .mls(mlsFeatureConfig): - updateOrCreate( - featureName: .mlsMigration, - isEnabled: mLSMigrationFeatureConfig.status == .enabled, - config: mLSMigrationFeatureConfig.toDomainModel() - ) + return ( + .mls, + mlsFeatureConfig.status == .enabled, + mlsFeatureConfig.toDomainModel() + ) - case let .selfDeletingMessages(selfDeletingMessagesFeatureConfig): + case let .mlsMigration(mLSMigrationFeatureConfig): - updateOrCreate( - featureName: .selfDeletingMessages, - isEnabled: selfDeletingMessagesFeatureConfig.status == .enabled, - config: selfDeletingMessagesFeatureConfig.toDomainModel() - ) + return ( + .mlsMigration, + mLSMigrationFeatureConfig.status == .enabled, + mLSMigrationFeatureConfig.toDomainModel() + ) - case let .unknown(featureName): + case let .selfDeletingMessages(selfDeletingMessagesFeatureConfig): - logger.warn( - "Unknown feature name: \(featureName)" - ) - } - } - } + return ( + .selfDeletingMessages, + selfDeletingMessagesFeatureConfig.status == .enabled, + selfDeletingMessagesFeatureConfig.toDomainModel() + ) - private func updateOrCreate( - featureName: Feature.Name, - isEnabled: Bool, - config: (any Codable)? = nil - ) { - if let config { - let encoder = JSONEncoder() - - do { - let data = try encoder.encode(config) - - Feature.updateOrCreate( - havingName: featureName, - in: context - ) { - $0.status = isEnabled ? .enabled : .disabled - $0.config = data - } - - } catch { - logger.error( - "Failed to encode \(String(describing: config.self)) : \(error)" - ) - } + case let .unknown(featureName): + logger.warn( + "Unknown feature name: \(featureName)" + ) - } else { - Feature.updateOrCreate( - havingName: featureName, - in: context - ) { - $0.status = isEnabled ? .enabled : .disabled - } + return nil } } } - -/// A feature fetched locally - -struct LocalFeature { - let status: Feature.Status - let config: T? -} - -/// The state of the feature - -struct FeatureState { - let name: Feature.Name - let status: FeatureConfigStatus - let shouldNotifyUser: Bool -} diff --git a/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/Models/FeatureState.swift b/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/Models/FeatureState.swift new file mode 100644 index 00000000000..8a50b6ce260 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/Models/FeatureState.swift @@ -0,0 +1,27 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel + +/// The state of the feature + +struct FeatureState { + let name: Feature.Name + let isEnabled: Bool + let shouldNotifyUser: Bool +} diff --git a/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/Models/LocalFeature.swift b/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/Models/LocalFeature.swift new file mode 100644 index 00000000000..a090534cf91 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/FeatureConfig/Models/LocalFeature.swift @@ -0,0 +1,25 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel + +/// A feature fetched locally +struct LocalFeature { + let status: Feature.Status + let config: T? +} diff --git a/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift index df7e5860432..8c231ff56ef 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift @@ -16,7 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import CoreData import WireDataModel // sourcery: AutoMockable @@ -37,15 +36,18 @@ public final class MessageLocalStore: MessageLocalStoreProtocol { let context: NSManagedObjectContext let conversationLocalStore: any ConversationLocalStoreProtocol + let userLocalStore: any UserLocalStoreProtocol // MARK: - Object lifecycle public init( context: NSManagedObjectContext, - conversationLocalStore: any ConversationLocalStoreProtocol + conversationLocalStore: any ConversationLocalStoreProtocol, + userLocalStore: any UserLocalStoreProtocol ) { self.context = context self.conversationLocalStore = conversationLocalStore + self.userLocalStore = userLocalStore } // MARK: - Public @@ -114,6 +116,54 @@ public final class MessageLocalStore: MessageLocalStoreProtocol { return [systemMessage] + case let .participantsRemoved(participants, sender, date): + + let removedUsers = await context.perform { + participants.compactMap { id, domain in + let existing = conversation.localParticipants + + return existing.first(where: { + $0.remoteIdentifier == id && $0.domain == domain + }) + } + } + + guard let sender = await fetchUser( + id: sender.id, + domain: sender.domain + ) else { + return [] + } + + let systemMessage = await createSystemMessage( + messageType: .participantsRemoved, + sender: sender, + users: Set(removedUsers), + timestamp: date + ) + + return [systemMessage] + + case let .participantsAdded(participants, sender, date): + guard let sender = await fetchUser( + id: sender.id, + domain: sender.domain + ) else { + return [] + } + + let newUsers = await userLocalStore.fetchOrCreateUsers( + userIDs: participants + ) + + let systemMessage = await createSystemMessage( + messageType: .participantsAdded, + sender: sender, + users: newUsers + ) + + return [systemMessage] + case .mlsMigrationMLSNotSupportedForSelfUser: let selfUser = await fetchSelfUser() @@ -159,27 +209,6 @@ public final class MessageLocalStore: MessageLocalStoreProtocol { return [systemMessage] - case let .participantRemoved(participant, sender, date): - - guard let removedParticipant = await fetchUser( - id: participant.id, - domain: participant.domain - ) else { return [] } - - let sender = await fetchUser( - id: sender.id, - domain: sender.domain - ) - - let systemMessage = await createSystemMessage( - messageType: .participantsRemoved, - sender: sender ?? removedParticipant, - users: Set([removedParticipant]), - timestamp: date - ) - - return [systemMessage] - case let .newConversationCreated(date): let selfUser = await fetchSelfUser() @@ -312,6 +341,72 @@ public final class MessageLocalStore: MessageLocalStoreProtocol { timestamp: date ) + return [systemMessage] + + case let .messageTimerUpdate(sender, date, timeoutValue): + + guard let sender = await fetchUser( + id: sender.id, + domain: sender.domain + ) else { + return [] + } + + let systemMessage = await createSystemMessage( + messageType: .messageTimerUpdate, + sender: sender, + users: [sender], + timestamp: date, + messageTimer: timeoutValue + ) + + return [systemMessage] + + case let .conversationNameChanged(newName, sender, date): + guard let sender = await fetchUser( + id: sender.id, + domain: sender.domain + ) else { + return [] + } + + let systemMessage = await createSystemMessage( + messageType: .conversationNameChanged, + sender: sender, + timestamp: date + ) + + await context.perform { + systemMessage.text = newName + systemMessage.visibleInConversation = conversation + conversation.updateTimestampsAfterUpdatingMessage(systemMessage) + } + + return [systemMessage] + + case let .readReceiptsStatus(isEnabled, sender, date): + guard let sender = await fetchUser( + id: sender.id, + domain: sender.domain + ) else { + return [] + } + + let systemMessage = await createSystemMessage( + messageType: isEnabled ? .readReceiptsEnabled : .readReceiptsDisabled, + sender: sender, + timestamp: date + ) + + await context.perform { + let isArchived = conversation.isArchived + let mutedMessageTypes = conversation.mutedMessageTypes + + if isArchived, mutedMessageTypes == .none { + conversation.isArchived = false + } + } + return [systemMessage] } } @@ -365,24 +460,17 @@ public final class MessageLocalStore: MessageLocalStoreProtocol { } } - // swiftlint:disable:next todo_requires_jira_link - // TODO: Use UserLocalStore when related PRs are merged. private func fetchUser( id: UUID, domain: String? ) async -> ZMUser? { - await context.perform { [context] in - ZMUser.fetch( - with: id, - domain: domain, - in: context - ) - } + try? await userLocalStore.fetchUser( + id: id, + domain: domain + ) } private func fetchSelfUser() async -> ZMUser { - await context.perform { [context] in - ZMUser.selfUser(in: context) - } + await userLocalStore.fetchSelfUser() } } diff --git a/WireDomain/Sources/WireDomain/Repositories/Message/Models/MessageType.swift b/WireDomain/Sources/WireDomain/Repositories/Message/Models/MessageType.swift index 352e197b3e4..ecb7c0ae782 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Message/Models/MessageType.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Message/Models/MessageType.swift @@ -29,6 +29,18 @@ public enum MessageType: Sendable { date: Date ) + case participantsRemoved( + participants: [(id: UUID, domain: String?)], + sender: (id: UUID, domain: String?), + date: Date + ) + + case participantsAdded( + participants: [(id: UUID, domain: String?)], + sender: (id: UUID, domain: String?), + date: Date + ) + case mlsMigrationMLSNotSupportedForSelfUser case mlsMigrationMLSNotSupportedForOtherUser( @@ -40,13 +52,13 @@ public enum MessageType: Sendable { date: Date ) - case participantRemoved( - participant: (id: UUID, domain: String?), - sender: (id: UUID, domain: String?), + case newConversationCreated( date: Date ) - case newConversationCreated( + case conversationNameChanged( + newName: String, + sender: (id: UUID, domain: String?), date: Date ) @@ -68,4 +80,16 @@ public enum MessageType: Sendable { case receiptModeIsOn( date: Date ) + + case messageTimerUpdate( + sender: (id: UUID, domain: String?), + date: Date, + timeoutValue: Double + ) + + case readReceiptsStatus( + isEnabled: Bool, + sender: (id: UUID, domain: String?), + date: Date + ) } diff --git a/WireDomain/Sources/WireDomain/Repositories/Team/Models/TeamMemberInfo.swift b/WireDomain/Sources/WireDomain/Repositories/Team/Models/TeamMemberInfo.swift new file mode 100644 index 00000000000..4247ad4c9cb --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/Team/Models/TeamMemberInfo.swift @@ -0,0 +1,26 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +public struct TeamMemberInfo: Sendable { + let id: UUID + let selfPermission: Int64? + let creatorID: UUID? + let creationDate: Date? +} diff --git a/WireAnalytics/Sources/WireAnalytics/Countly/Countly+Types.swift b/WireDomain/Sources/WireDomain/Repositories/Team/Models/TeamRoleInfo.swift similarity index 86% rename from WireAnalytics/Sources/WireAnalytics/Countly/Countly+Types.swift rename to WireDomain/Sources/WireDomain/Repositories/Team/Models/TeamRoleInfo.swift index 1ebe660b533..64dce169b51 100644 --- a/WireAnalytics/Sources/WireAnalytics/Countly/Countly+Types.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Team/Models/TeamRoleInfo.swift @@ -16,8 +16,9 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Countly +import Foundation -public typealias WireCountly = Countly - -public typealias WireCountlyConfig = CountlyConfig +public struct TeamRoleInfo: Sendable { + let role: String + let actions: [String] +} diff --git a/WireDomain/Sources/WireDomain/Repositories/Team/TeamLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/Team/TeamLocalStore.swift new file mode 100644 index 00000000000..add4f74b6fa --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/Team/TeamLocalStore.swift @@ -0,0 +1,319 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel + +// sourcery: AutoMockable +public protocol TeamLocalStoreProtocol { + + func fetchMember( + id: UUID + ) async -> Member? + + /// Fetches the self user ID. + /// - returns: The self user ID. + + func selfUserID() async -> UUID + + /// Fetches the user membership. + /// - parameter user: A given user. + /// - returns: The user membership. + + func userMembership( + user: ZMUser + ) async -> Member? + + /// Fetches the user domain. + /// - parameter user: A given user. + /// - returns: The user domain. + + func userDomain( + user: ZMUser + ) async -> String? + + /// Deletes the member locally. + /// - parameter member: A given member. + + func deleteMember( + _ member: Member + ) async + + /// Stores a flag whether the member needs backend update. + /// - parameters: + /// - needsBackendUpdate: The flag to update. + /// - member: A given member. + + func storeMember( + needsBackendUpdate: Bool, + member: Member + ) async + + /// Stores a team locally. + /// - Parameters: + /// - id: The team ID. + /// - name: The team name. + /// - creatorID: The team creator ID. + /// - logoID: The team logo ID. + /// - logoKey: The team logo key. + + func storeTeam( + id: UUID, + name: String, + creatorID: UUID, + logoID: String?, + logoKey: String? + ) async + + /// Stores team roles locally. + /// - parameters: + /// - selfTeamID: The self team ID. + /// - teamRolesInfo: A list of role and actions. + + func storeTeamRoles( + selfTeamID: UUID, + teamRolesInfo: [TeamRoleInfo] + ) async throws + + /// Stores team members locally. + /// - parameters: + /// - selfTeamID: The self team ID. + /// - teamMembersInfo: A list of member info (id, permission, creator id, date) + + func storeTeamMembers( + selfTeamID: UUID, + teamMembersInfo: [TeamMemberInfo] + ) async throws + + /// Fetches self user info : user ID and client ID. + /// - returns: the user ID and the client ID. + + func selfUserInfo() async -> (id: UUID, clientId: String?) +} + +public final class TeamLocalStore: TeamLocalStoreProtocol { + + // MARK: - Error + + enum Error: Swift.Error { + /// The local team instance was not found in the database. + + case teamNotFoundLocally + } + + // MARK: - Properties + + private let context: NSManagedObjectContext + private let userLocalStore: any UserLocalStoreProtocol + + // MARK: - Object lifecycle + + public init( + context: NSManagedObjectContext, + userLocalStore: any UserLocalStoreProtocol + ) { + self.context = context + self.userLocalStore = userLocalStore + } + + // MARK: - Public + + public func fetchMember( + id: UUID + ) async -> Member? { + await context.perform { [context] in + Member.fetch( + with: id, + in: context + ) + } + } + + public func selfUserID() async -> UUID { + let selfUser = await userLocalStore.fetchSelfUser() + + return await context.perform { + selfUser.remoteIdentifier + } + } + + public func userMembership( + user: ZMUser + ) async -> Member? { + await context.perform { + user.membership + } + } + + public func userDomain( + user: ZMUser + ) async -> String? { + await context.perform { + user.domain + } + } + + public func deleteMember( + _ member: Member + ) async { + await context.perform { [context] in + context.delete(member) + } + } + + public func storeMember( + needsBackendUpdate: Bool, + member: Member + ) async { + await context.perform { [context] in + member.needsToBeUpdatedFromBackend = true + context.saveOrRollback() + } + } + + public func storeTeam( + id: UUID, + name: String, + creatorID: UUID, + logoID: String?, + logoKey: String? + ) async { + let selfUser = await userLocalStore.fetchSelfUser() + + await context.perform { [context] in + let team = WireDataModel.Team.fetchOrCreate( + with: id, + in: context + ) + + _ = WireDataModel.Member.getOrUpdateMember( + for: selfUser, + in: team, + context: context + ) + + team.name = name + team.creator = ZMUser.fetchOrCreate( + with: creatorID, + domain: nil, + in: context + ) + team.pictureAssetId = logoID + team.pictureAssetKey = logoKey + team.needsToBeUpdatedFromBackend = false + } + } + + public func storeTeamRoles( + selfTeamID: UUID, + teamRolesInfo: [TeamRoleInfo] + ) async throws { + try await context.perform { [context, selfTeamID] in + guard let team = WireDataModel.Team.fetch( + with: selfTeamID, + in: context + ) else { + throw Error.teamNotFoundLocally + } + + let existingRoles = team.roles + + let localRoles = teamRolesInfo.map { teamRoleInfo in + let localRole = WireDataModel.Role.fetchOrCreate( + name: teamRoleInfo.role, + teamOrConversation: .team(team), + context: context + ) + + localRole.name = teamRoleInfo.role + localRole.team = team + + for action in teamRoleInfo.actions { + let action = Action.fetchOrCreate( + name: action, + in: context + ) + + localRole.actions.insert(action) + } + + return localRole + } + + for roleToDelete in existingRoles.subtracting(localRoles) { + context.delete(roleToDelete) + } + + team.needsToDownloadRoles = false + } + } + + public func storeTeamMembers( + selfTeamID: UUID, + teamMembersInfo: [TeamMemberInfo] + ) async throws { + try await context.perform { [context, selfTeamID] in + guard let team = WireDataModel.Team.fetch( + with: selfTeamID, + in: context + ) else { + throw Error.teamNotFoundLocally + } + + for teamMemberInfo in teamMembersInfo { + let user = ZMUser.fetchOrCreate( + with: teamMemberInfo.id, + domain: nil, + in: context + ) + + let membership = Member.getOrUpdateMember( + for: user, + in: team, + context: context + ) + + if let selfPermission = teamMemberInfo.selfPermission { + membership.permissions = Permissions(rawValue: selfPermission) + } + + if let creatorID = teamMemberInfo.creatorID { + membership.createdBy = ZMUser.fetchOrCreate( + with: creatorID, + domain: nil, + in: context + ) + } + + membership.createdAt = teamMemberInfo.creationDate + membership.needsToBeUpdatedFromBackend = false + } + } + } + + public func selfUserInfo() async -> (id: UUID, clientId: String?) { + let selfUser = await userLocalStore.fetchSelfUser() + + return await context.perform { + ( + id: selfUser.remoteIdentifier, + clientId: selfUser.selfClient()?.remoteIdentifier + ) + } + } + +} diff --git a/WireDomain/Sources/WireDomain/Repositories/Team/TeamModelMappings.swift b/WireDomain/Sources/WireDomain/Repositories/Team/TeamModelMappings.swift new file mode 100644 index 00000000000..c4ca824f569 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/Team/TeamModelMappings.swift @@ -0,0 +1,70 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireAPI + +extension WireAPI.ConversationRole { + + func toDomainModel() -> TeamRoleInfo { + .init( + role: name, + actions: actions.map(\.name) + ) + } + +} + +extension WireAPI.TeamMember { + + func toDomainModel() -> TeamMemberInfo { + .init( + id: userID, + selfPermission: permissions?.selfPermissions, + creatorID: creatorID, + creationDate: creationDate + ) + } + +} + +private extension WireAPI.ConversationAction { + + var name: String { + switch self { + case .addConversationMember: + "add_conversation_member" + case .removeConversationMember: + "remove_conversation_member" + case .modifyConversationName: + "modify_conversation_name" + case .modifyConversationMessageTimer: + "modify_conversation_message_timer" + case .modifyConversationReceiptMode: + "modify_conversation_receipt_mode" + case .modifyConversationAccess: + "modify_conversation_access" + case .modifyOtherConversationMember: + "modify_other_conversation_member" + case .leaveConversation: + "leave_conversation" + case .deleteConversation: + "delete_conversation" + } + } + +} diff --git a/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepository.swift b/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepository.swift index 3d2f646dc6c..3f0d77f6bc8 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepository.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepository.swift @@ -40,25 +40,32 @@ public protocol TeamRepositoryProtocol { func pullSelfTeamMembers() async throws - /// Fetch the legalhold status for the self user from the server. + /// Fetches the legalhold info for the self user from the server. + /// - returns: The legalhold info. - func fetchSelfLegalholdStatus() async throws -> LegalholdStatus + func fetchSelfLegalholdInfo() async throws -> TeamMemberLegalholdInfo /// Deletes the member of a team. /// - Parameter userID: The ID of the team member. /// - Parameter domain: The domain of the team member. - /// - Parameter time: The time the member left the team. + /// - Parameter date: The time the member left the team. func deleteMembership( - for userID: UUID, + userID: UUID, domain: String?, - at time: Date + date: Date ) async throws /// Sets the team member `needsToBeUpdatedFromBackend` flag to true. /// - Parameter membershipID: The id of the team member. - func storeTeamMemberNeedsBackendUpdate(membershipID: UUID) async throws + func storeTeamMemberNeedsBackendUpdate( + membershipID: UUID + ) async throws + + /// Pulls and stores legalhold info locally. + + func pullSelfLegalholdInfo() async throws } @@ -69,103 +76,147 @@ public class TeamRepository: TeamRepositoryProtocol { private let selfTeamID: UUID private let userRepository: any UserRepositoryProtocol private let teamsAPI: any TeamsAPI - // swiftlint:disable:next todo_requires_jira_link - // TODO: create TeamLocalStore - private let context: NSManagedObjectContext + private let teamLocalStore: any TeamLocalStoreProtocol // MARK: - Object lifecycle public init( selfTeamID: UUID, userRepository: any UserRepositoryProtocol, - teamsAPI: any TeamsAPI, - context: NSManagedObjectContext + teamLocalStore: any TeamLocalStoreProtocol, + teamsAPI: any TeamsAPI ) { self.selfTeamID = selfTeamID self.userRepository = userRepository + self.teamLocalStore = teamLocalStore self.teamsAPI = teamsAPI - self.context = context } // MARK: - Public public func pullSelfTeam() async throws { let team = try await fetchSelfTeamRemotely() - await storeTeamLocally(team) + + await teamLocalStore.storeTeam( + id: team.id, + name: team.name, + creatorID: team.creatorID, + logoID: team.logoID, + logoKey: team.logoKey + ) } public func pullSelfTeamRoles() async throws { let teamRoles = try await fetchSelfTeamRolesRemotely() - try await storeTeamRolesLocally(teamRoles) + + let teamRolesInfo = teamRoles.map { + $0.toDomainModel() + } + + try await teamLocalStore.storeTeamRoles( + selfTeamID: selfTeamID, + teamRolesInfo: teamRolesInfo + ) } public func pullSelfTeamMembers() async throws { let teamMembers = try await fetchSelfTeamMembersRemotely() - try await storeTeamMembersLocally(teamMembers) + + let teamMembersInfo = teamMembers.map { + $0.toDomainModel() + } + + try await teamLocalStore.storeTeamMembers( + selfTeamID: selfTeamID, + teamMembersInfo: teamMembersInfo + ) } public func fetchSelfLegalholdStatus() async throws -> LegalholdStatus { - let selfUser = await userRepository.fetchSelfUser() + let selfUserID = await teamLocalStore.selfUserID() - let selfUserID: UUID = await context.perform { - selfUser.remoteIdentifier - } - - return try await teamsAPI.getLegalholdStatus( + return try await teamsAPI.getLegalholdInfo( for: selfTeamID, userID: selfUserID - ) + ).status } public func deleteMembership( - for userID: UUID, + userID: UUID, domain: String?, - at time: Date + date: Date ) async throws { let user = try await userRepository.fetchUser( id: userID, domain: domain ) - let member = try await context.perform { - guard let member = user.membership else { - throw TeamRepositoryError.userNotAMemberInTeam(user: userID) - } - - return member + guard let member = await teamLocalStore.userMembership( + user: user + ) else { + throw TeamRepositoryError.userNotAMemberInTeam(user: userID) } - let domain = await context.perform { - user.domain - } + let domain = await teamLocalStore.userDomain(user: user) try await userRepository.deleteUserAccount( id: userID, domain: domain, - at: time + at: date ) - await context.perform { [context] in - context.delete(member) - } + await teamLocalStore.deleteMember(member) } public func storeTeamMemberNeedsBackendUpdate(membershipID: UUID) async throws { - try await context.perform { [context] in + guard let member = await teamLocalStore.fetchMember( + id: membershipID + ) else { + throw TeamRepositoryError.failedToFindTeamMember(membershipID) + } + + await teamLocalStore.storeMember( + needsBackendUpdate: true, + member: member + ) + } + + public func pullSelfLegalholdInfo() async throws { + let selfUser = await userRepository.fetchSelfUser() - guard let member = Member.fetch( - with: membershipID, - in: context - ) else { - throw TeamRepositoryError.failedToFindTeamMember(membershipID) + let (selfUserID, selfClientID) = await teamLocalStore.selfUserInfo() + + let selfUserLegalHold = try await fetchSelfLegalholdInfo() + + switch selfUserLegalHold.status { + case .pending: + guard let selfClientID else { + return } - member.needsToBeUpdatedFromBackend = true + await userRepository.addLegalHoldRequest( + userID: selfUserID, + clientID: selfClientID, + lastPrekey: selfUserLegalHold.prekey + ) - try context.save() + case .disabled: + await userRepository.disableUserLegalHold() + + default: + break } } + public func fetchSelfLegalholdInfo() async throws -> TeamMemberLegalholdInfo { + let (selfUserID, _) = await teamLocalStore.selfUserInfo() + + return try await teamsAPI.getLegalholdInfo( + for: selfTeamID, + userID: selfUserID + ) + } + // MARK: - Private private func fetchSelfTeamRemotely() async throws -> WireAPI.Team { @@ -176,33 +227,6 @@ public class TeamRepository: TeamRepositoryProtocol { } } - private func storeTeamLocally(_ teamAPIModel: WireAPI.Team) async { - let selfUser = await userRepository.fetchSelfUser() - - await context.perform { [context] in - let team = WireDataModel.Team.fetchOrCreate( - with: teamAPIModel.id, - in: context - ) - - _ = WireDataModel.Member.getOrUpdateMember( - for: selfUser, - in: team, - context: context - ) - - team.name = teamAPIModel.name - team.creator = ZMUser.fetchOrCreate( - with: teamAPIModel.creatorID, - domain: nil, - in: context - ) - team.pictureAssetId = teamAPIModel.logoID - team.pictureAssetKey = teamAPIModel.logoKey - team.needsToBeUpdatedFromBackend = false - } - } - private func fetchSelfTeamRolesRemotely() async throws -> [WireAPI.ConversationRole] { do { return try await teamsAPI.getTeamRoles(for: selfTeamID) @@ -211,47 +235,6 @@ public class TeamRepository: TeamRepositoryProtocol { } } - private func storeTeamRolesLocally(_ roles: [WireAPI.ConversationRole]) async throws { - try await context.perform { [context, selfTeamID] in - guard let team = WireDataModel.Team.fetch( - with: selfTeamID, - in: context - ) else { - throw TeamRepositoryError.teamNotFoundLocally - } - - let existingRoles = team.roles - - let localRoles = roles.map { role in - let localRole = Role.fetchOrCreate( - name: role.name, - teamOrConversation: .team(team), - context: context - ) - - localRole.name = role.name - localRole.team = team - - for action in role.actions { - let action = Action.fetchOrCreate( - name: action.name, - in: context - ) - - localRole.actions.insert(action) - } - - return localRole - } - - for roleToDelete in existingRoles.subtracting(localRoles) { - context.delete(roleToDelete) - } - - team.needsToDownloadRoles = false - } - } - private func fetchSelfTeamMembersRemotely() async throws -> [WireAPI.TeamMember] { do { return try await teamsAPI.getTeamMembers( @@ -263,71 +246,4 @@ public class TeamRepository: TeamRepositoryProtocol { } } - private func storeTeamMembersLocally(_ teamMembers: [WireAPI.TeamMember]) async throws { - try await context.perform { [context, selfTeamID] in - guard let team = WireDataModel.Team.fetch( - with: selfTeamID, - in: context - ) else { - throw TeamRepositoryError.teamNotFoundLocally - } - - for member in teamMembers { - let user = ZMUser.fetchOrCreate( - with: member.userID, - domain: nil, - in: context - ) - - let membership = Member.getOrUpdateMember( - for: user, - in: team, - context: context - ) - - if let permissions = member.permissions { - membership.permissions = Permissions(rawValue: permissions.selfPermissions) - } - - if let creatorID = member.creatorID { - membership.createdBy = ZMUser.fetchOrCreate( - with: creatorID, - domain: nil, - in: context - ) - } - - membership.createdAt = member.creationDate - membership.needsToBeUpdatedFromBackend = false - } - } - } - -} - -private extension ConversationAction { - - var name: String { - switch self { - case .addConversationMember: - "add_conversation_member" - case .removeConversationMember: - "remove_conversation_member" - case .modifyConversationName: - "modify_conversation_name" - case .modifyConversationMessageTimer: - "modify_conversation_message_timer" - case .modifyConversationReceiptMode: - "modify_conversation_receipt_mode" - case .modifyConversationAccess: - "modify_conversation_access" - case .modifyOtherConversationMember: - "modify_other_conversation_member" - case .leaveConversation: - "leave_conversation" - case .deleteConversation: - "delete_conversation" - } - } - } diff --git a/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepositoryError.swift b/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepositoryError.swift index 6b119bb7f20..48591d6f90a 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepositoryError.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepositoryError.swift @@ -26,10 +26,6 @@ enum TeamRepositoryError: Error { case failedToFetchRemotely(Error) - /// The local team instance was not found in the database. - - case teamNotFoundLocally - /// User is not a member of the team. case userNotAMemberInTeam(user: UUID) diff --git a/WireDomain/Sources/WireDomain/Repositories/UpdateEvents/UpdateEventsLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/UpdateEvents/UpdateEventsLocalStore.swift new file mode 100644 index 00000000000..fbb13e0cd9d --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/UpdateEvents/UpdateEventsLocalStore.swift @@ -0,0 +1,165 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel +import WireFoundation +import WireLogging + +// sourcery: AutoMockable +protocol UpdateEventsLocalStoreProtocol { + + /// Get last event ID. + /// - returns: The last event ID. + + func lastEventID() -> UUID? + + /// Stores last event ID. + /// - parameter id: The last event ID to store. + + func storeLastEventID(id: UUID) + + /// Retrieves the index of the last event envelope. + /// - returns: The last index event envelope. + + func indexOfLastEventEnvelope() async throws -> Int64 + + /// Persists an event envelope locally. + /// - Parameters: + /// - data: The event envelope payload data. + /// - index: The event envelope index. + + func persistEventEnvelope( + _ data: Data, + index: Int64 + ) async throws + + /// Fetches stored event envelope payloads. + /// - parameter limit: A fetch limit. + /// - returns: A list of event payloads. + + func fetchStoredEventEnvelopePayloads( + limit: UInt + ) async throws -> [Data] + + /// Deletes next pending events locally. + /// - parameter limit: A fetch limit. + + func deleteNextPendingEvents( + limit: UInt + ) async throws +} + +final class UpdateEventsLocalStore: UpdateEventsLocalStoreProtocol { + + enum Key: String, DefaultsKey { + case lastEventID + } + + // MARK: - Error + + enum Error: Swift.Error { + case failedToFetchStoredEvents(Swift.Error) + case failedToDeleteStoredEvents(Swift.Error) + } + + // MARK: - Properties + + private let context: NSManagedObjectContext + private let storage: PrivateUserDefaults + + // MARK: - Object lifecycle + + init( + context: NSManagedObjectContext, + userID: UUID, + sharedUserDefaults: UserDefaults + ) { + self.context = context + self.storage = PrivateUserDefaults( + userID: userID, + storage: sharedUserDefaults + ) + } + + // MARK: - Public + + public func lastEventID() -> UUID? { + storage.getUUID( + forKey: .lastEventID + ) + } + + public func storeLastEventID(id: UUID) { + storage.setUUID(id, forKey: .lastEventID) + } + + public func indexOfLastEventEnvelope() async throws -> Int64 { + try await context.perform { [context] in + let request = StoredUpdateEventEnvelope.sortedFetchRequest(asending: false) + request.fetchBatchSize = 1 + let lastEnvelope = try context.fetch(request).first + return lastEnvelope?.sortIndex ?? 0 + } + } + + public func persistEventEnvelope( + _ data: Data, + index: Int64 + ) async throws { + try await context.perform { [context] in + let storedEventEnvelope = StoredUpdateEventEnvelope(context: context) + storedEventEnvelope.data = data + storedEventEnvelope.sortIndex = index + try context.save() + } + } + + public func fetchStoredEventEnvelopePayloads( + limit: UInt + ) async throws -> [Data] { + try await context.perform { [context] in + do { + let request = StoredUpdateEventEnvelope.sortedFetchRequest(asending: true) + request.fetchLimit = Int(limit) + request.returnsObjectsAsFaults = false + let storedEventEnvelopes = try context.fetch(request) + return storedEventEnvelopes.map(\.data) + } catch { + throw Error.failedToFetchStoredEvents(error) + } + } + } + + public func deleteNextPendingEvents( + limit: UInt + ) async throws { + try await context.perform { [context] in + do { + let request = StoredUpdateEventEnvelope.sortedFetchRequest(asending: true) + request.fetchLimit = Int(limit) + let storedEventEnvelopes = try context.fetch(request) + WireLogger.sync.debug("deleting \(storedEventEnvelopes.count) stored envelopes") + storedEventEnvelopes.forEach(context.delete) + try context.save() + } catch { + throw Error.failedToDeleteStoredEvents(error) + } + } + } + +} diff --git a/WireDomain/Sources/WireDomain/Repositories/UpdateEvents/UpdateEventsRepository.swift b/WireDomain/Sources/WireDomain/Repositories/UpdateEvents/UpdateEventsRepository.swift index bd6fe3c14e5..73ec07cf81e 100644 --- a/WireDomain/Sources/WireDomain/Repositories/UpdateEvents/UpdateEventsRepository.swift +++ b/WireDomain/Sources/WireDomain/Repositories/UpdateEvents/UpdateEventsRepository.swift @@ -20,6 +20,7 @@ import Foundation import WireAPI import WireDataModel import WireFoundation +import WireLogging // sourcery: AutoMockable /// Access update events. @@ -85,10 +86,6 @@ protocol UpdateEventsRepositoryProtocol { final class UpdateEventsRepository: UpdateEventsRepositoryProtocol { - enum Key: String, DefaultsKey { - case lastEventID - } - // MARK: - Properties private let userID: UUID @@ -96,11 +93,7 @@ final class UpdateEventsRepository: UpdateEventsRepositoryProtocol { private let updateEventsAPI: any UpdateEventsAPI private let pushChannel: any PushChannelProtocol private let updateEventDecryptor: any UpdateEventDecryptorProtocol - // swiftlint:disable:next todo_requires_jira_link - // TODO: create UpdateEventsLocalStore - private let eventContext: NSManagedObjectContext - private let storage: PrivateUserDefaults - + private let updateEventsLocalStore: any UpdateEventsLocalStoreProtocol private let encoder = JSONEncoder() private let decoder = JSONDecoder() @@ -112,19 +105,14 @@ final class UpdateEventsRepository: UpdateEventsRepositoryProtocol { updateEventsAPI: any UpdateEventsAPI, pushChannel: any PushChannelProtocol, updateEventDecryptor: any UpdateEventDecryptorProtocol, - eventContext: NSManagedObjectContext, - sharedUserDefaults: UserDefaults + updateEventsLocalStore: any UpdateEventsLocalStoreProtocol ) { self.userID = userID self.selfClientID = selfClientID self.updateEventsAPI = updateEventsAPI self.pushChannel = pushChannel self.updateEventDecryptor = updateEventDecryptor - self.eventContext = eventContext - self.storage = PrivateUserDefaults( - userID: userID, - storage: sharedUserDefaults - ) + self.updateEventsLocalStore = updateEventsLocalStore } // MARK: - Pull pending events @@ -132,12 +120,12 @@ final class UpdateEventsRepository: UpdateEventsRepositoryProtocol { func pullPendingEvents() async throws { WireLogger.sync.debug("pulling pending events") // We want all events since this event. - guard let lastEventID = storage.getUUID(forKey: .lastEventID) else { + guard let lastEventID = updateEventsLocalStore.lastEventID() else { throw UpdateEventsRepositoryError.lastEventIDMissing } // We'll insert new events from this index. - var currentIndex = try await indexOfLastEventEnvelope() + 1 + var currentIndex = try await updateEventsLocalStore.indexOfLastEventEnvelope() + 1 // Events are fetched in batches. for try await envelopes in updateEventsAPI.getUpdateEvents( @@ -168,8 +156,10 @@ final class UpdateEventsRepository: UpdateEventsRepositoryProtocol { attributes: [.eventEnvelopeID: envelope.id] ) - try await persistEventEnvelope( - decryptedEnvelope, + let decryptedEnvelopeData = try encoder.encode(decryptedEnvelope) + + try await updateEventsLocalStore.persistEventEnvelope( + decryptedEnvelopeData, index: currentIndex ) @@ -192,49 +182,13 @@ final class UpdateEventsRepository: UpdateEventsRepositoryProtocol { storeLastEventEnvelopeID(lastEvent.id) } - private func indexOfLastEventEnvelope() async throws -> Int64 { - try await eventContext.perform { [eventContext] in - let request = StoredUpdateEventEnvelope.sortedFetchRequest(asending: false) - request.fetchBatchSize = 1 - let lastEnvelope = try eventContext.fetch(request).first - return lastEnvelope?.sortIndex ?? 0 - } - } - - private func persistEventEnvelope( - _ eventEnvelope: UpdateEventEnvelope, - index: Int64 - ) async throws { - try await eventContext.perform { [eventContext, encoder] in - let data = try encoder.encode(eventEnvelope) - let storedEventEnvelope = StoredUpdateEventEnvelope(context: eventContext) - storedEventEnvelope.data = data - storedEventEnvelope.sortIndex = index - try eventContext.save() - } - } - // MARK: - Fetch pending events func fetchNextPendingEvents(limit: UInt) async throws -> [UpdateEventEnvelope] { - let payloads = try await fetchStoredEventEnvelopePayloads(limit: limit) + let payloads = try await updateEventsLocalStore.fetchStoredEventEnvelopePayloads(limit: limit) return try decodeEventEnvelopes(payloads) } - private func fetchStoredEventEnvelopePayloads(limit: UInt) async throws -> [Data] { - try await eventContext.perform { [eventContext] in - do { - let request = StoredUpdateEventEnvelope.sortedFetchRequest(asending: true) - request.fetchLimit = Int(limit) - request.returnsObjectsAsFaults = false - let storedEventEnvelopes = try eventContext.fetch(request) - return storedEventEnvelopes.map(\.data) - } catch { - throw UpdateEventsRepositoryError.failedToFetchStoredEvents(error) - } - } - } - private func decodeEventEnvelopes(_ payloads: [Data]) throws -> [UpdateEventEnvelope] { try payloads.map { do { @@ -248,18 +202,7 @@ final class UpdateEventsRepository: UpdateEventsRepositoryProtocol { // MARK: - Delete pending events func deleteNextPendingEvents(limit: UInt) async throws { - try await eventContext.perform { [eventContext] in - do { - let request = StoredUpdateEventEnvelope.sortedFetchRequest(asending: true) - request.fetchLimit = Int(limit) - let storedEventEnvelopes = try eventContext.fetch(request) - WireLogger.sync.debug("deleting \(storedEventEnvelopes.count) stored envelopes") - storedEventEnvelopes.forEach(eventContext.delete) - try eventContext.save() - } catch { - throw UpdateEventsRepositoryError.failedToDeleteStoredEvents(error) - } - } + try await updateEventsLocalStore.deleteNextPendingEvents(limit: limit) } // MARK: - Live events @@ -294,7 +237,7 @@ final class UpdateEventsRepository: UpdateEventsRepositoryProtocol { attributes: [.eventEnvelopeID: id] ) - storage.setUUID(id, forKey: .lastEventID) + updateEventsLocalStore.storeLastEventID(id: id) } } diff --git a/WireDomain/Sources/WireDomain/Repositories/UpdateEvents/UpdateEventsRepositoryError.swift b/WireDomain/Sources/WireDomain/Repositories/UpdateEvents/UpdateEventsRepositoryError.swift index 4d8703798e6..68828db00f3 100644 --- a/WireDomain/Sources/WireDomain/Repositories/UpdateEvents/UpdateEventsRepositoryError.swift +++ b/WireDomain/Sources/WireDomain/Repositories/UpdateEvents/UpdateEventsRepositoryError.swift @@ -21,8 +21,6 @@ import Foundation enum UpdateEventsRepositoryError: Error { case lastEventIDMissing - case failedToFetchStoredEvents(Error) case failedToDecodeStoredEvent(Error) - case failedToDeleteStoredEvents(Error) } diff --git a/WireDomain/Sources/WireDomain/Repositories/User/Models/NewUserInfo.swift b/WireDomain/Sources/WireDomain/Repositories/User/Models/NewUserInfo.swift new file mode 100644 index 00000000000..33cdb554efa --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/User/Models/NewUserInfo.swift @@ -0,0 +1,35 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel + +public struct NewUserInfo: Sendable { + let userID: WireDataModel.QualifiedID + let name: String + let handle: String? + let teamID: UUID? + let accentID: Int + let previewAssetKey: String? + let completeAssetKey: String? + let deleted: Bool? + let email: String? + let expiresAt: Date? + let serviceID: UUID? + let serviceProvider: UUID? + let supportedProtocols: Set? +} diff --git a/WireDomain/Sources/WireDomain/Repositories/User/Models/UserUpdateInfo.swift b/WireDomain/Sources/WireDomain/Repositories/User/Models/UserUpdateInfo.swift new file mode 100644 index 00000000000..c405eae35c4 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/User/Models/UserUpdateInfo.swift @@ -0,0 +1,31 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel + +public struct UserUpdateInfo: Sendable { + let userID: UUID + let accentColorID: Int? + let name: String? + let handle: String? + let email: String? + let isSSOIDDeleted: Bool? + let previewAssetKey: String? + let completeAssetKey: String? + let supportedProtocols: Set? +} diff --git a/WireDomain/Sources/WireDomain/Repositories/User/UserLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/User/UserLocalStore.swift index e209b17ad69..3ecf82ca186 100644 --- a/WireDomain/Sources/WireDomain/Repositories/User/UserLocalStore.swift +++ b/WireDomain/Sources/WireDomain/Repositories/User/UserLocalStore.swift @@ -16,9 +16,8 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import CoreData -import WireAPI import WireDataModel +import WireLogging // sourcery: AutoMockable /// A local store dedicated to user. @@ -52,32 +51,19 @@ public protocol UserLocalStoreProtocol { domain: String? ) async -> ZMUser - /// Removes user push token from storage. - - func deletePushToken() - - /// Fetches or creates a user client locally. + /// Fetches or creates users locally. /// /// - parameters: - /// - id: The user client id to find or create locally. - /// - returns: The user client found or created locally and a flag indicating whether or not the user client is new. + /// - userIDs: The users id to fetch or create locally. + /// - returns: A list of users fetched or created locally. - func fetchOrCreateUserClient( - id: String - ) async -> (client: WireDataModel.UserClient, isNew: Bool) + func fetchOrCreateUsers( + userIDs: [(id: UUID, domain: String?)] + ) async -> Set - /// Updates the user client informations locally. - /// - /// - parameters: - /// - localClient: The user client to update locally. - /// - remoteClient: The up-to-date remote user client. - /// - isNewClient: A flag indicating whether the user client is new. + /// Removes user push token from storage. - func updateUserClient( - _ localClient: WireDataModel.UserClient, - from remoteClient: WireAPI.SelfUserClient, - isNewClient: Bool - ) async throws + func deletePushToken() /// Adds a legal hold request to self. /// @@ -133,9 +119,14 @@ public protocol UserLocalStoreProtocol { func markAccountAsDeleted(for user: ZMUser) async - // TODO: [WPB-10727] Merge these two methods into a single method (also no API objects should be passed to local store) - func persistUser(from user: WireAPI.User) async - func updateUser(from event: UserUpdateEvent) async + // TODO: [WPB-10727] Merge these two methods into a single method + func persistUser(userInfo: NewUserInfo) async + func updateUser(userUpdateInfo: UserUpdateInfo) async + + /// Fetches all user IDs that have a one on one conversation + /// - returns: A list of users' qualified IDs. + + func fetchAllUserIDsWithOneOnOneConversation() async throws -> [WireDataModel.QualifiedID] } public final class UserLocalStore: UserLocalStoreProtocol { @@ -195,6 +186,26 @@ public final class UserLocalStore: UserLocalStoreProtocol { } } + public func fetchAllUserIDsWithOneOnOneConversation() async throws -> [WireDataModel.QualifiedID] { + try await context.perform { [context] in + let request = NSFetchRequest(entityName: ZMUser.entityName()) + let predicate = NSPredicate(format: "%K != nil", #keyPath(ZMUser.oneOnOneConversation)) + request.predicate = predicate + + return try context + .fetch(request) + .compactMap { user in + guard let userID = user.qualifiedID else { + WireLogger.conversation.error( + "Missing user's qualifiedID" + ) + return nil + } + return userID + } + } + } + public func fetchUsersQualifiedIDs() async throws -> [WireDataModel.QualifiedID] { try await context.perform { let fetchRequest = NSFetchRequest(entityName: ZMUser.entityName()) @@ -254,20 +265,21 @@ public final class UserLocalStore: UserLocalStoreProtocol { } } - public func fetchOrCreateUserClient( - id: String - ) async -> (client: WireDataModel.UserClient, isNew: Bool) { + public func fetchOrCreateUsers( + userIDs: [(id: UUID, domain: String?)] + ) async -> Set { + await context.perform { [context] in - if let existingClient = UserClient.fetchExistingUserClient( - with: id, - in: context - ) { - return (existingClient, false) - } else { - let newClient = UserClient.insertNewObject(in: context) - newClient.remoteIdentifier = id - return (newClient, true) + + let users = userIDs.map { + ZMUser.fetchOrCreate( + with: $0.id, + domain: $0.domain, + in: context + ) } + + return Set(users) } } @@ -290,54 +302,52 @@ public final class UserLocalStore: UserLocalStoreProtocol { } } - // swiftlint:disable:next todo_requires_jira_link - // TODO: refactor, do not pass API object (WireAPI.UserClient) directly, merge this method with updateUser method. - public func persistUser(from user: WireAPI.User) async { + public func persistUser(userInfo: NewUserInfo) async { let persistedUser = await fetchOrCreateUser( - id: user.id.uuid, - domain: user.id.domain + id: userInfo.userID.uuid, + domain: userInfo.userID.domain ) await context.perform { - guard user.deleted == false else { + guard userInfo.deleted == false else { return persistedUser.markAccountAsDeleted(at: Date()) } - persistedUser.name = user.name - persistedUser.handle = user.handle - persistedUser.teamIdentifier = user.teamID - persistedUser.accentColorValue = Int16(user.accentID) - persistedUser.previewProfileAssetIdentifier = user.assets.first(where: { $0.size == .preview })?.key - persistedUser.previewProfileAssetIdentifier = user.assets.first(where: { $0.size == .complete })?.key - persistedUser.emailAddress = user.email - persistedUser.expiresAt = user.expiresAt - persistedUser.serviceIdentifier = user.service?.id.transportString() - persistedUser.providerIdentifier = user.service?.provider.transportString() - persistedUser.supportedProtocols = user.supportedProtocols?.toDomainModel() ?? [.proteus] + persistedUser.name = userInfo.name + persistedUser.handle = userInfo.handle + persistedUser.teamIdentifier = userInfo.teamID + persistedUser.accentColorValue = Int16(userInfo.accentID) + persistedUser.previewProfileAssetIdentifier = userInfo.previewAssetKey + persistedUser.previewProfileAssetIdentifier = userInfo.completeAssetKey + persistedUser.emailAddress = userInfo.email + persistedUser.expiresAt = userInfo.expiresAt + persistedUser.serviceIdentifier = userInfo.serviceID?.transportString() + persistedUser.providerIdentifier = userInfo.serviceProvider?.transportString() + persistedUser.supportedProtocols = userInfo.supportedProtocols ?? [.proteus] persistedUser.needsToBeUpdatedFromBackend = false } } // TODO: [WPB-10727] reuse `updateUserMetadata` from mentioned ticket's implementation to avoid code duplication - public func updateUser(from event: UserUpdateEvent) async { + public func updateUser(userUpdateInfo: UserUpdateInfo) async { let user = await fetchOrCreateUser( - id: event.userID + id: userUpdateInfo.userID ) await context.perform { - if let name = event.name { + if let name = userUpdateInfo.name { user.name = name } - if let email = event.email { + if let email = userUpdateInfo.email { user.emailAddress = email } - if let handle = event.handle { + if let handle = userUpdateInfo.handle { user.handle = handle } - if let accentColor = event.accentColorID { + if let accentColor = userUpdateInfo.accentColorID { user.accentColorValue = Int16(accentColor) } @@ -350,13 +360,9 @@ public final class UserLocalStore: UserLocalStoreProtocol { /// changes to its assets /// we don't want to update them and keep these changes as is until they're synced. if !user.hasLocalModifications(forKeys: assetKeys) { - let previewAssetKey = event.assets? - .first(where: { $0.size == .preview }) - .map(\.key) + let previewAssetKey = userUpdateInfo.previewAssetKey - let completeAssetKey = event.assets? - .first(where: { $0.size == .complete }) - .map(\.key) + let completeAssetKey = userUpdateInfo.completeAssetKey if let previewAssetKey { user.previewProfileAssetIdentifier = previewAssetKey @@ -367,68 +373,9 @@ public final class UserLocalStore: UserLocalStoreProtocol { } } - user.supportedProtocols = event.supportedProtocols?.toDomainModel() ?? [.proteus] + user.supportedProtocols = userUpdateInfo.supportedProtocols ?? [.proteus] user.isPendingMetadataRefresh = false } } - - // swiftlint:disable:next todo_requires_jira_link - // TODO: refactor, do not pass API object (WireAPI.UserClient) directly - public func updateUserClient( - _ localClient: WireDataModel.UserClient, - from remoteClient: WireAPI.SelfUserClient, - isNewClient: Bool - ) async throws { - await context.perform { [context] in - - localClient.label = remoteClient.label - localClient.type = remoteClient.type.toDomainModel() - localClient.model = remoteClient.model - localClient.deviceClass = remoteClient.deviceClass?.toDomainModel() - localClient.activationDate = remoteClient.activationDate - localClient.lastActiveDate = remoteClient.lastActiveDate - localClient.remoteIdentifier = remoteClient.id - - let selfUser = ZMUser.selfUser(in: context) - localClient.user = localClient.user ?? selfUser - - if isNewClient { - localClient.needsSessionMigration = selfUser.domain == nil - } - - if localClient.isLegalHoldDevice, isNewClient { - selfUser.legalHoldRequest = nil - selfUser.needsToAcknowledgeLegalHoldStatus = true - } - - if !localClient.isSelfClient() { - localClient.mlsPublicKeys = .init( - ed25519: remoteClient.mlsPublicKeys?.ed25519, - ed448: remoteClient.mlsPublicKeys?.ed448, - p256: remoteClient.mlsPublicKeys?.p256, - p384: remoteClient.mlsPublicKeys?.p384, - p521: remoteClient.mlsPublicKeys?.p512 - ) - } - - let selfClient = selfUser.selfClient() - let isNotSameId = localClient.remoteIdentifier != selfClient?.remoteIdentifier - let localClientActivationDate = localClient.activationDate - let selfClientActivationDate = selfClient?.activationDate - - if selfClient != nil, isNotSameId, let localClientActivationDate, let selfClientActivationDate { - let comparisonResult = localClientActivationDate - .compare(selfClientActivationDate) - - if comparisonResult == .orderedDescending { - localClient.needsToNotifyUser = true - } - } - - selfUser.selfClient()?.addNewClientToIgnored(localClient) - selfUser.selfClient()?.updateSecurityLevelAfterDiscovering(Set([localClient])) - } - } - } diff --git a/WireDomain/Sources/WireDomain/Repositories/User/UserModelMappings.swift b/WireDomain/Sources/WireDomain/Repositories/User/UserModelMappings.swift index 5e1f11e7ac3..b2e5da40fd8 100644 --- a/WireDomain/Sources/WireDomain/Repositories/User/UserModelMappings.swift +++ b/WireDomain/Sources/WireDomain/Repositories/User/UserModelMappings.swift @@ -103,3 +103,79 @@ extension WireAPI.Prekey { } } + +extension WireAPI.UserUpdateEvent { + + func toDomainModel() -> UserUpdateInfo { + .init( + userID: userID, + accentColorID: accentColorID, + name: name, + handle: handle, + email: email, + isSSOIDDeleted: isSSOIDDeleted, + previewAssetKey: assets? + .first(where: { $0.size == .preview }) + .map(\.key), + completeAssetKey: assets? + .first(where: { $0.size == .complete }) + .map(\.key), + supportedProtocols: supportedProtocols?.toDomainModel() + ) + } + +} + +extension WireAPI.User { + + func toDomainModel() -> NewUserInfo { + + .init( + userID: id.toDomainModel(), + name: name, + handle: handle, + teamID: teamID, + accentID: accentID, + previewAssetKey: assets + .first(where: { $0.size == .preview }) + .map(\.key), + completeAssetKey: assets + .first(where: { $0.size == .complete }) + .map(\.key), + deleted: deleted, + email: email, + expiresAt: expiresAt, + serviceID: service?.id, + serviceProvider: service?.provider, + supportedProtocols: supportedProtocols?.toDomainModel() + ) + + } + +} + +extension WireAPI.SelfUser { + + func toDomainModel() -> NewUserInfo { + .init( + userID: qualifiedID.toDomainModel(), + name: name, + handle: handle, + teamID: teamID, + accentID: accentID, + previewAssetKey: assets? + .first(where: { $0.size == .preview }) + .map(\.key), + completeAssetKey: assets? + .first(where: { $0.size == .complete }) + .map(\.key), + deleted: deleted, + email: email, + expiresAt: expiresAt, + serviceID: service?.id, + serviceProvider: service?.provider, + supportedProtocols: supportedProtocols?.toDomainModel() + ) + } + +} diff --git a/WireDomain/Sources/WireDomain/Repositories/User/UserRepository.swift b/WireDomain/Sources/WireDomain/Repositories/User/UserRepository.swift index a4823583947..2a1427d61f6 100644 --- a/WireDomain/Sources/WireDomain/Repositories/User/UserRepository.swift +++ b/WireDomain/Sources/WireDomain/Repositories/User/UserRepository.swift @@ -20,6 +20,7 @@ import Foundation import WireAPI import WireDataModel import WireFoundation +import WireLogging // sourcery: AutoMockable /// Facilitate access to users related domain objects. @@ -29,6 +30,10 @@ import WireFoundation /// as well as the possible source(s) of the models. public protocol UserRepositoryProtocol { + /// Pulls self user and stores it locally + + func pullSelfUser() async throws + /// Fetch self user from the local store func fetchSelfUser() async -> ZMUser @@ -149,6 +154,11 @@ public protocol UserRepositoryProtocol { domain: String? ) async throws -> Bool + /// Fetches all user IDs that have a one on one conversation + /// - returns: A list of users' qualified IDs. + + func fetchAllUserIDsWithOneOnOneConversation() async throws -> [WireDataModel.QualifiedID] + } public final class UserRepository: UserRepositoryProtocol { @@ -179,6 +189,14 @@ public final class UserRepository: UserRepositoryProtocol { // MARK: - Public + public func pullSelfUser() async throws { + let selfUser = try await selfUserAPI.getSelfUser() + + await userLocalStore.persistUser( + userInfo: selfUser.toDomainModel() + ) + } + public func fetchSelfUser() async -> ZMUser { await userLocalStore.fetchSelfUser() } @@ -226,7 +244,7 @@ public final class UserRepository: UserRepositoryProtocol { let userList = try await usersAPI.getUsers(userIDs: userIDs.toAPIModel()) for user in userList.found { - await userLocalStore.persistUser(from: user) + await userLocalStore.persistUser(userInfo: user.toDomainModel()) } } catch { @@ -238,7 +256,7 @@ public final class UserRepository: UserRepositoryProtocol { from event: UserUpdateEvent ) async { await userLocalStore.updateUser( - from: event + userUpdateInfo: event.toDomainModel() ) } @@ -333,6 +351,10 @@ public final class UserRepository: UserRepositoryProtocol { } } + public func fetchAllUserIDsWithOneOnOneConversation() async throws -> [WireDataModel.QualifiedID] { + try await userLocalStore.fetchAllUserIDsWithOneOnOneConversation() + } + public func isSelfUser( id: UUID, domain: String? diff --git a/WireDomain/Sources/WireDomain/Repositories/UserClients/Models/UserClientInfo.swift b/WireDomain/Sources/WireDomain/Repositories/UserClients/Models/UserClientInfo.swift new file mode 100644 index 00000000000..f0544d2f714 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/UserClients/Models/UserClientInfo.swift @@ -0,0 +1,40 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel + +public struct UserClientInfo: Sendable { + + let id: String + let label: String? + let type: WireDataModel.DeviceType + let activationDate: Date + let model: String? + let deviceClass: WireDataModel.DeviceClass? + let lastActiveDate: Date? + let mlsPublicKeys: UserClientInfo.MLSPublicKeys? + + struct MLSPublicKeys { + let ed25519: String? + let ed448: String? + let p256: String? + let p384: String? + let p512: String? + } + +} diff --git a/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsLocalStore.swift new file mode 100644 index 00000000000..8ff2478db73 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsLocalStore.swift @@ -0,0 +1,229 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel +import WireLogging + +// sourcery: AutoMockable +public protocol UserClientsLocalStoreProtocol { + + /// Fetches or creates a client locally. + /// + /// - parameters: + /// - id: The user client id to find or create locally. + /// - returns: The user client found or created locally and a flag indicating whether or not the user client is new. + + func fetchOrCreateClient( + id: String + ) async -> (client: WireDataModel.UserClient, isNew: Bool) + + /// Retrieves deleted self clients locally based on new self clients. + /// - parameter newClients: The new self user clients. + /// - returns: A list of deleted self clients. + + func deletedSelfClients( + newClients: [String] + ) async -> [String] + + /// Deletes client locally. + /// - parameter id: The client id. + + func deleteClient( + id: String + ) async + + /// Updates the user client informations locally. + /// + /// - parameters: + /// - id: The user client id. + /// - isNewClient: A flag indicating whether the user client is new. + /// - remoteClient: The up-to-date user client info object. + + func updateClient( + id: String, + isNewClient: Bool, + userClientInfo: UserClientInfo + ) async + + /// Indicates whether self user clients are active MLS clients. + /// - returns: A flag indicating whether all self user clients are active MLS clients. + + func allSelfUserClientsAreActiveMLSClients() async -> Bool +} + +public final class UserClientsLocalStore: UserClientsLocalStoreProtocol { + + // MARK: - Properties + + private let context: NSManagedObjectContext + private let userLocalStore: any UserLocalStoreProtocol + + // MARK: - Object lifecycle + + init( + context: NSManagedObjectContext, + userLocalStore: any UserLocalStoreProtocol + ) { + self.context = context + self.userLocalStore = userLocalStore + } + + public func fetchOrCreateClient( + id: String + ) async -> (client: WireDataModel.UserClient, isNew: Bool) { + await context.perform { [context] in + if let existingClient = UserClient.fetchExistingUserClient( + with: id, + in: context + ) { + return (existingClient, false) + } else { + let newClient = UserClient.insertNewObject(in: context) + newClient.remoteIdentifier = id + return (newClient, true) + } + } + } + + public func deletedSelfClients( + newClients: [String] + ) async -> [String] { + let selfUser = await userLocalStore.fetchSelfUser() + + return await context.perform { + selfUser.clients + .compactMap(\.remoteIdentifier) + .filter { + !newClients.contains($0) + } + } + } + + public func deleteClient( + id: String + ) async { + let localClient = await context.perform { [context] in + return UserClient.fetchExistingUserClient( + with: id, + in: context + ) + } + + guard let localClient else { + return WireLogger.userClient.error( + "Failed to find existing client with id: \(id.redactedAndTruncated())" + ) + } + + await localClient.deleteClientAndEndSession() + } + + public func updateClient( + id: String, + isNewClient: Bool, + userClientInfo: UserClientInfo + ) async { + await context.perform { [context] in + + guard let localClient = UserClient.fetchExistingUserClient( + with: id, + in: context + ) else { + return WireLogger.userClient.error( + "Failed to find existing client with id: \(id.redactedAndTruncated())" + ) + } + + localClient.label = userClientInfo.label + localClient.type = userClientInfo.type + localClient.model = userClientInfo.model + localClient.deviceClass = userClientInfo.deviceClass + localClient.activationDate = userClientInfo.activationDate + localClient.lastActiveDate = userClientInfo.lastActiveDate + localClient.remoteIdentifier = userClientInfo.id + + let selfUser = ZMUser.selfUser(in: context) + localClient.user = localClient.user ?? selfUser + + if isNewClient { + localClient.needsSessionMigration = selfUser.domain == nil + } + + if localClient.isLegalHoldDevice, isNewClient { + selfUser.legalHoldRequest = nil + selfUser.needsToAcknowledgeLegalHoldStatus = true + } + + if !localClient.isSelfClient() { + localClient.mlsPublicKeys = .init( + ed25519: userClientInfo.mlsPublicKeys?.ed25519, + ed448: userClientInfo.mlsPublicKeys?.ed448, + p256: userClientInfo.mlsPublicKeys?.p256, + p384: userClientInfo.mlsPublicKeys?.p384, + p521: userClientInfo.mlsPublicKeys?.p512 + ) + } + + let selfClient = selfUser.selfClient() + let isNotSameId = localClient.remoteIdentifier != selfClient?.remoteIdentifier + let localClientActivationDate = localClient.activationDate + let selfClientActivationDate = selfClient?.activationDate + + if selfClient != nil, isNotSameId, let localClientActivationDate, let selfClientActivationDate { + let comparisonResult = localClientActivationDate + .compare(selfClientActivationDate) + + if comparisonResult == .orderedDescending { + localClient.needsToNotifyUser = true + } + } + + selfUser.selfClient()?.addNewClientToIgnored(localClient) + selfUser.selfClient()?.updateSecurityLevelAfterDiscovering(Set([localClient])) + } + } + + public func allSelfUserClientsAreActiveMLSClients() async -> Bool { + let selfUser = await userLocalStore.fetchSelfUser() + + return await context.perform { + selfUser.clients.all { userClient in + let hasMLSIdentity = !userClient.mlsPublicKeys.isEmpty + + let isRecentlyActive: Bool = { + if userClient.isSelfClient() { + return true + } + + guard let lastActiveDate = userClient.lastActiveDate else { + return false + } + + guard lastActiveDate <= Date() else { + return true + } + + return lastActiveDate.timeIntervalSinceNow.magnitude < .fourWeeks + }() + + return hasMLSIdentity && isRecentlyActive + } + } + } + +} diff --git a/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsModelMappings.swift b/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsModelMappings.swift new file mode 100644 index 00000000000..23c0c5f77f7 --- /dev/null +++ b/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsModelMappings.swift @@ -0,0 +1,42 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireAPI + +extension WireAPI.SelfUserClient { + + func toDomainModel() -> UserClientInfo { + .init( + id: id, + label: label, + type: type.toDomainModel(), + activationDate: activationDate, + model: model, + deviceClass: deviceClass?.toDomainModel(), + lastActiveDate: lastActiveDate, + mlsPublicKeys: .init( + ed25519: mlsPublicKeys?.ed25519, + ed448: mlsPublicKeys?.ed448, + p256: mlsPublicKeys?.p256, + p384: mlsPublicKeys?.p384, + p512: mlsPublicKeys?.p512 + ) + ) + } + +} diff --git a/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsRepository.swift b/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsRepository.swift index 02309b8bec6..a2435614519 100644 --- a/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsRepository.swift +++ b/WireDomain/Sources/WireDomain/Repositories/UserClients/UserClientsRepository.swift @@ -16,7 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import CoreData import Foundation import WireAPI import WireDataModel @@ -42,7 +41,7 @@ public protocol UserClientsRepositoryProtocol { /// - returns: The user client found or created locally and a flag indicating whether or not the user client is new. func fetchOrCreateClient( - with id: String + id: String ) async throws -> (client: WireDataModel.UserClient, isNew: Bool) /// Updates the user client informations locally. @@ -53,7 +52,7 @@ public protocol UserClientsRepositoryProtocol { /// - isNewClient: A flag indicating whether the user client is new. func updateClient( - with id: String, + id: String, from remoteClient: WireAPI.SelfUserClient, isNewClient: Bool ) async throws @@ -61,7 +60,7 @@ public protocol UserClientsRepositoryProtocol { /// Deletes client locally. /// - parameter id: The client id. - func deleteClient(with id: String) async + func deleteClient(id: String) async /// Indicates whether self user clients are active MLS clients. /// - returns: A flag indicating whether all self user clients are active MLS clients. @@ -75,175 +74,73 @@ public struct UserClientsRepository: UserClientsRepositoryProtocol { private let userClientsAPI: any UserClientsAPI private let userRepository: any UserRepositoryProtocol - private let context: NSManagedObjectContext + private let userClientsLocalStore: any UserClientsLocalStoreProtocol // MARK: - Object lifecycle init( userClientsAPI: any UserClientsAPI, userRepository: any UserRepositoryProtocol, - context: NSManagedObjectContext + userClientsLocalStore: any UserClientsLocalStoreProtocol ) { self.userClientsAPI = userClientsAPI self.userRepository = userRepository - self.context = context + self.userClientsLocalStore = userClientsLocalStore } // MARK: - Public + public func fetchOrCreateClient( + id: String + ) async throws -> (client: WireDataModel.UserClient, isNew: Bool) { + await userClientsLocalStore.fetchOrCreateClient( + id: id + ) + } + + public func deleteClient( + id: String + ) async { + await userClientsLocalStore.deleteClient(id: id) + } + public func pullSelfClients() async throws { let remoteSelfClients = try await userClientsAPI.getSelfClients() - let selfUser = await userRepository.fetchSelfUser() - let localSelfClients = await context.perform { - selfUser.clients - } for remoteSelfClient in remoteSelfClients { - let localUserClient = try await fetchOrCreateClient(with: remoteSelfClient.id) + let localUserClient = await userClientsLocalStore.fetchOrCreateClient( + id: remoteSelfClient.id + ) + try await updateClient( - with: remoteSelfClient.id, + id: remoteSelfClient.id, from: remoteSelfClient, isNewClient: localUserClient.isNew ) } - let deletedSelfClientsIDs = await context.perform { - localSelfClients - .compactMap(\.remoteIdentifier) - .filter { - !remoteSelfClients.map(\.id).contains($0) - } - } + let deletedSelfClientsIDs = await userClientsLocalStore.deletedSelfClients( + newClients: remoteSelfClients.map(\.id) + ) for deletedSelfClientID in deletedSelfClientsIDs { - await deleteClient(with: deletedSelfClientID) - } - } - - public func fetchOrCreateClient( - with id: String - ) async throws -> (client: WireDataModel.UserClient, isNew: Bool) { - await context.perform { [context] in - if let existingClient = UserClient.fetchExistingUserClient( - with: id, - in: context - ) { - return (existingClient, false) - } else { - let newClient = UserClient.insertNewObject(in: context) - newClient.remoteIdentifier = id - return (newClient, true) - } + await userClientsLocalStore.deleteClient(id: deletedSelfClientID) } } public func updateClient( - with id: String, + id: String, from remoteClient: WireAPI.SelfUserClient, isNewClient: Bool ) async throws { - await context.perform { [context] in - - guard let localClient = UserClient.fetchExistingUserClient( - with: id, - in: context - ) else { - return WireLogger.userClient.error( - "Failed to find existing client with id: \(id.redactedAndTruncated())" - ) - } - - localClient.label = remoteClient.label - localClient.type = remoteClient.type.toDomainModel() - localClient.model = remoteClient.model - localClient.deviceClass = remoteClient.deviceClass?.toDomainModel() - localClient.activationDate = remoteClient.activationDate - localClient.lastActiveDate = remoteClient.lastActiveDate - localClient.remoteIdentifier = remoteClient.id - - let selfUser = ZMUser.selfUser(in: context) - localClient.user = localClient.user ?? selfUser - - if isNewClient { - localClient.needsSessionMigration = selfUser.domain == nil - } - - if localClient.isLegalHoldDevice, isNewClient { - selfUser.legalHoldRequest = nil - selfUser.needsToAcknowledgeLegalHoldStatus = true - } - - if !localClient.isSelfClient() { - localClient.mlsPublicKeys = .init( - ed25519: remoteClient.mlsPublicKeys?.ed25519, - ed448: remoteClient.mlsPublicKeys?.ed448, - p256: remoteClient.mlsPublicKeys?.p256, - p384: remoteClient.mlsPublicKeys?.p384, - p521: remoteClient.mlsPublicKeys?.p512 - ) - } - - let selfClient = selfUser.selfClient() - let isNotSameId = localClient.remoteIdentifier != selfClient?.remoteIdentifier - let localClientActivationDate = localClient.activationDate - let selfClientActivationDate = selfClient?.activationDate - - if selfClient != nil, isNotSameId, let localClientActivationDate, let selfClientActivationDate { - let comparisonResult = localClientActivationDate - .compare(selfClientActivationDate) - - if comparisonResult == .orderedDescending { - localClient.needsToNotifyUser = true - } - } - - selfUser.selfClient()?.addNewClientToIgnored(localClient) - selfUser.selfClient()?.updateSecurityLevelAfterDiscovering(Set([localClient])) - } - } - - public func deleteClient(with id: String) async { - let localClient = await context.perform { - UserClient.fetchExistingUserClient( - with: id, - in: context - ) - } - - guard let localClient else { - return WireLogger.userClient.error( - "Failed to find existing client with id: \(id.redactedAndTruncated())" - ) - } - - await localClient.deleteClientAndEndSession() + await userClientsLocalStore.updateClient( + id: id, + isNewClient: isNewClient, + userClientInfo: remoteClient.toDomainModel() + ) } public func allSelfUserClientsAreActiveMLSClients() async -> Bool { - let selfUser = await userRepository.fetchSelfUser() - - return await context.perform { - selfUser.clients.all { userClient in - let hasMLSIdentity = !userClient.mlsPublicKeys.isEmpty - - let isRecentlyActive: Bool = { - if userClient.isSelfClient() { - return true - } - - guard let lastActiveDate = userClient.lastActiveDate else { - return false - } - - guard lastActiveDate <= Date() else { - return true - } - - return lastActiveDate.timeIntervalSinceNow.magnitude < .fourWeeks - }() - - return hasMLSIdentity && isRecentlyActive - } - } + await userClientsLocalStore.allSelfUserClientsAreActiveMLSClients() } } diff --git a/WireDomain/Sources/WireDomain/OneOnOneResolver.swift b/WireDomain/Sources/WireDomain/Synchronization/OneOnOneResolver.swift similarity index 73% rename from WireDomain/Sources/WireDomain/OneOnOneResolver.swift rename to WireDomain/Sources/WireDomain/Synchronization/OneOnOneResolver.swift index a6a326fb96d..91c54b7b37b 100644 --- a/WireDomain/Sources/WireDomain/OneOnOneResolver.swift +++ b/WireDomain/Sources/WireDomain/Synchronization/OneOnOneResolver.swift @@ -19,21 +19,21 @@ import CoreData import WireAPI import WireDataModel +import WireLogging // sourcery: AutoMockable /// Resolves 1:1 conversations public protocol OneOnOneResolverProtocol { - func invoke() async throws + + func resolveOneOnOneConversation( + with userID: WireDataModel.QualifiedID + ) async throws + + func resolveAllOneOnOneConversations() async throws } struct OneOnOneResolver: OneOnOneResolverProtocol { - /// The target to resolve 1:1 conversation for. - enum Target { - case user(id: WireAPI.QualifiedID) - case allUsers - } - private enum Error: Swift.Error { case failedToActivateConversation case failedToFetchConversation @@ -43,11 +43,9 @@ struct OneOnOneResolver: OneOnOneResolverProtocol { // MARK: - Properties private let context: NSManagedObjectContext - private let target: Target private let userRepository: any UserRepositoryProtocol private let conversationsRepository: any ConversationRepositoryProtocol - private let mlsService: any MLSServiceInterface - private let isMLSEnabled: Bool + private let mlsProvider: MLSProvider // MARK: - Object lifecycle @@ -55,50 +53,50 @@ struct OneOnOneResolver: OneOnOneResolverProtocol { context: NSManagedObjectContext, userRepository: any UserRepositoryProtocol, conversationsRepository: any ConversationRepositoryProtocol, - mlsService: any MLSServiceInterface, - isMLSEnabled: Bool, - target: Target + mlsProvider: MLSProvider ) { self.context = context self.userRepository = userRepository self.conversationsRepository = conversationsRepository - self.mlsService = mlsService - self.isMLSEnabled = isMLSEnabled - self.target = target + self.mlsProvider = mlsProvider } - // MARK: - Public - - public func invoke() async throws { - switch target { - case let .user(id): - try await resolveOneOnOneConversation(with: id.toDomainModel()) - case .allUsers: - // TODO: [WPB-10727] resolve all users 1:1 conversations - break + func resolveAllOneOnOneConversations() async throws { + let usersIDs = try await userRepository.fetchAllUserIDsWithOneOnOneConversation() + + await withTaskGroup(of: Void.self) { group in + for userID in usersIDs { + group.addTask { + do { + try await resolveOneOnOneConversation(with: userID) + } catch { + /// skip conversation migration for this user + WireLogger.conversation.error( + "resolve 1-1 conversation with userID \(userID) failed!" + ) + } + } + } } } - // MARK: - Private - - private func resolveOneOnOneConversation( + func resolveOneOnOneConversation( with userID: WireDataModel.QualifiedID ) async throws { let user = try await userRepository.fetchUser( - id: userID.uuid, - domain: userID.domain + id: userID.uuid, domain: userID.domain ) let selfUser = await userRepository.fetchSelfUser() let commonProtocol = await getCommonProtocol(between: selfUser, and: user) - if isMLSEnabled, commonProtocol == .mls { + if mlsProvider.isMLSEnabled, commonProtocol == .mls { try await resolveMLSConversation( for: user ) } - if isMLSEnabled, commonProtocol == nil { + if mlsProvider.isMLSEnabled, commonProtocol == nil { await resolveNoCommonProtocolConversation( between: selfUser, and: user @@ -130,27 +128,27 @@ struct OneOnOneResolver: OneOnOneResolverProtocol { ) /// Then, fetch the synced MLS conversation. - let mlsConversation = await conversationsRepository.fetchMLSConversation( - groupID: mlsGroupID - ) + let mlsConversation = await conversationsRepository.fetchMLSConversation(groupID: mlsGroupID) - let localMLSGroupID = await context.perform { + let groupID = await context.perform { mlsConversation?.mlsGroupID } - guard let mlsConversation, let localMLSGroupID else { + guard let mlsConversation, let groupID else { throw Error.failedToFetchConversation } + let mlsService = mlsProvider.service + /// If conversation already exists, there is no need to perform a migration. let needsMLSMigration = try await mlsService.conversationExists( - groupID: localMLSGroupID + groupID: groupID ) == false if needsMLSMigration { await migrateToMLS( mlsConversation: mlsConversation, - mlsGroupID: localMLSGroupID, + mlsGroupID: groupID, user: user, userID: userID ) @@ -197,6 +195,8 @@ struct OneOnOneResolver: OneOnOneResolverProtocol { groupID: MLSGroupID, userID: WireDataModel.QualifiedID ) async throws { + let mlsService = mlsProvider.service + let epoch = await context.perform { mlsConversation.epoch } @@ -253,19 +253,15 @@ struct OneOnOneResolver: OneOnOneResolverProtocol { private func resolveProteusConversation( for user: ZMUser ) async { - WireLogger.conversation.debug("Should resolve to Proteus 1-1 conversation") - - let conversation = await context.perform { - user.oneOnOneConversation - } + await context.perform { + WireLogger.conversation.debug("Should resolve to Proteus 1-1 conversation") - guard let conversation else { - return WireLogger.conversation.warn( - "Failed to resolve Proteus conversation: missing 1:1 conversation for user with id \(user.remoteIdentifier.safeForLoggingDescription)" - ) - } + guard let conversation = user.oneOnOneConversation else { + return WireLogger.conversation.warn( + "Failed to resolve Proteus conversation: missing 1:1 conversation for user with id \(user.remoteIdentifier.safeForLoggingDescription)" + ) + } - await context.perform { conversation.isForcedReadOnly = false } } @@ -281,24 +277,16 @@ struct OneOnOneResolver: OneOnOneResolverProtocol { between selfUser: ZMUser, and user: ZMUser ) async { - WireLogger.conversation.debug("No common protocols found") - - let conversation = await context.perform { - user.oneOnOneConversation - } - - guard let conversation else { - return WireLogger.conversation.warn( - "Failed to resolve 1:1 conversation with no common protocol: missing 1:1 conversation for user with id \(user.remoteIdentifier.safeForLoggingDescription)" - ) - } + await context.perform { + WireLogger.conversation.debug("No common protocols found") - let isForcedReadOnly = await context.perform { - conversation.isForcedReadOnly - } + guard let conversation = user.oneOnOneConversation else { + return WireLogger.conversation.warn( + "Failed to resolve 1:1 conversation with no common protocol: missing 1:1 conversation for user with id \(user.remoteIdentifier.safeForLoggingDescription)" + ) + } - if !isForcedReadOnly { - await context.perform { + if !conversation.isForcedReadOnly { if !selfUser.supportedProtocols.contains(.mls) { conversation.appendMLSMigrationMLSNotSupportedForSelfUser(user: selfUser) } else if !user.supportedProtocols.contains(.mls) { @@ -314,22 +302,20 @@ struct OneOnOneResolver: OneOnOneResolverProtocol { between selfUser: ZMUser, and otherUser: ZMUser ) async -> ConversationMessageProtocol? { - let selfUserProtocols = await context.perform { - selfUser.supportedProtocols - } - - let otherUserProtocols = await context.perform { - otherUser.supportedProtocols.isEmpty ? [.proteus] : otherUser.supportedProtocols - } - - let commonProtocols = selfUserProtocols.intersection(otherUserProtocols) - - if commonProtocols.contains(.mls) { - return .mls - } else if commonProtocols.contains(.proteus) { - return .proteus - } else { - return nil + await context.perform { + let selfUserProtocols = selfUser.supportedProtocols + let otherUserProtocols = otherUser.supportedProtocols.isEmpty ? [.proteus] : otherUser + .supportedProtocols /// default to Proteus if empty. + + let commonProtocols = selfUserProtocols.intersection(otherUserProtocols) + + if commonProtocols.contains(.mls) { + return .mls + } else if commonProtocols.contains(.proteus) { + return .proteus + } else { + return nil + } } } } diff --git a/WireDomain/Sources/WireDomain/Synchronization/SyncManager.swift b/WireDomain/Sources/WireDomain/Synchronization/SyncManager.swift index ebee591d656..243fee52944 100644 --- a/WireDomain/Sources/WireDomain/Synchronization/SyncManager.swift +++ b/WireDomain/Sources/WireDomain/Synchronization/SyncManager.swift @@ -16,12 +16,19 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import CoreData import Foundation import WireAPI +import WireDataModel +import WireLogging import WireSystem protocol SyncManagerProtocol { + /// Pulls and stores all required objects for the database to be initially up-to-date. + + func performSlowSync() async throws + /// Fetch events from the server and process all pending events. func performQuickSync() async throws @@ -34,18 +41,88 @@ protocol SyncManagerProtocol { final class SyncManager: SyncManagerProtocol { + enum Failure: Error { + case failedToPerformSlowSync(Error) + } + + // MARK: - Properties + private(set) var syncState: SyncState = .suspended private var isSuspending = false + // MARK: - Repositories + private let updateEventsRepository: any UpdateEventsRepositoryProtocol + private let teamRepository: any TeamRepositoryProtocol + private let connectionsRepository: any ConnectionsRepositoryProtocol + private let conversationsRepository: any ConversationRepositoryProtocol + private let userRepository: any UserRepositoryProtocol + private let conversationLabelsRepository: any ConversationLabelsRepositoryProtocol + private let featureConfigsRepository: any FeatureConfigRepositoryProtocol + private let pushSupportedProtocolsUseCase: any PushSupportedProtocolsUseCaseProtocol + private let mlsProvider: MLSProvider + private let context: NSManagedObjectContext + + // MARK: - Update event processor + private let updateEventProcessor: any UpdateEventProcessorProtocol + // MARK: - Object lifecycle + init( updateEventsRepository: any UpdateEventsRepositoryProtocol, - updateEventProcessor: any UpdateEventProcessorProtocol + teamRepository: any TeamRepositoryProtocol, + connectionsRepository: any ConnectionsRepositoryProtocol, + conversationsRepository: any ConversationRepositoryProtocol, + userRepository: any UserRepositoryProtocol, + conversationLabelsRepository: any ConversationLabelsRepositoryProtocol, + featureConfigsRepository: any FeatureConfigRepositoryProtocol, + updateEventProcessor: any UpdateEventProcessorProtocol, + pushSupportedProtocolsUseCase: any PushSupportedProtocolsUseCaseProtocol, + mlsProvider: MLSProvider, + context: NSManagedObjectContext ) { self.updateEventsRepository = updateEventsRepository + self.teamRepository = teamRepository + self.connectionsRepository = connectionsRepository + self.conversationsRepository = conversationsRepository + self.userRepository = userRepository + self.conversationLabelsRepository = conversationLabelsRepository + self.featureConfigsRepository = featureConfigsRepository self.updateEventProcessor = updateEventProcessor + self.pushSupportedProtocolsUseCase = pushSupportedProtocolsUseCase + self.mlsProvider = mlsProvider + self.context = context + } + + func performSlowSync() async throws { + do { + try await updateEventsRepository.pullLastEventID() + try await teamRepository.pullSelfTeam() + try await teamRepository.pullSelfTeamRoles() + try await teamRepository.pullSelfTeamMembers() + try await connectionsRepository.pullConnections() + try await conversationsRepository.pullConversations() + try await userRepository.pullKnownUsers() + try await userRepository.pullSelfUser() + try await teamRepository.pullSelfLegalholdInfo() + try await conversationLabelsRepository.pullConversationLabels() + try await featureConfigsRepository.pullFeatureConfigs() + try await pushSupportedProtocolsUseCase.invoke() + let oneOnOneResolver = makeOneOnOneResolver() + try await oneOnOneResolver.resolveAllOneOnOneConversations() + } catch { + throw Failure.failedToPerformSlowSync(error) + } + } + + private func makeOneOnOneResolver() -> OneOnOneResolverProtocol { + OneOnOneResolver( + context: context, + userRepository: userRepository, + conversationsRepository: conversationsRepository, + mlsProvider: mlsProvider + ) } func performQuickSync() async throws { @@ -111,7 +188,7 @@ final class SyncManager: SyncManagerProtocol { isSuspending = false } - private var ongoingTask: Task? { + private var ongoingTask: Task? { switch syncState { case let .quickSync(task): task diff --git a/WireDomain/Sources/WireDomain/UseCases/IndividualToTeamMigrationUseCaseImplementation.swift b/WireDomain/Sources/WireDomain/UseCases/IndividualToTeamMigrationUseCaseImplementation.swift new file mode 100644 index 00000000000..0d4cc1aab90 --- /dev/null +++ b/WireDomain/Sources/WireDomain/UseCases/IndividualToTeamMigrationUseCaseImplementation.swift @@ -0,0 +1,49 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation +@preconcurrency import WireAPI +import WireDomainAPI +import WireLogging +import WireSystem + +public struct IndividualToTeamMigrationUseCaseImplementation: IndividualToTeamMigrationUseCase { + private let accountsAPI: AccountsAPI + private let logger: WireLogger = .individualToTeamMigration + + public init(apiService: APIServiceProtocol, apiVersion: APIVersion) { + self.accountsAPI = AccountsAPIBuilder(apiService: apiService).makeAPI(for: apiVersion) + } + + public func invoke(teamName: String) async throws -> IndividualToTeamMigrationResult { + logger.debug("Migrating individual account to team account") + do { + let upgradeResult = try await accountsAPI.upgradeToTeam(teamName: teamName) + logger.debug("Individual account migrated to team account") + return IndividualToTeamMigrationResult(teamID: upgradeResult.teamId, teamName: upgradeResult.teamName) + } catch { + logger.error("Failed to migrate individual account to team account") + switch error { + case AccountsAPIError.userAlreadyInATeam: + throw IndividualToTeamMigrationError.userAlreadyInTeam + default: + throw IndividualToTeamMigrationError.generic(error) + } + } + } +} diff --git a/WireDomain/Sources/WireDomain/UseCases/PushSupportedProtocolsUseCase.swift b/WireDomain/Sources/WireDomain/UseCases/PushSupportedProtocolsUseCase.swift index 1ba76b21a5e..43e7e83198e 100644 --- a/WireDomain/Sources/WireDomain/UseCases/PushSupportedProtocolsUseCase.swift +++ b/WireDomain/Sources/WireDomain/UseCases/PushSupportedProtocolsUseCase.swift @@ -18,10 +18,16 @@ import WireAPI import WireDataModel +import WireLogging import WireSystem +// sourcery: AutoMockable /// Calculates and pushes the supported protocols to the backend -public struct PushSupportedProtocolsUseCase { +public protocol PushSupportedProtocolsUseCaseProtocol { + func invoke() async throws +} + +public struct PushSupportedProtocolsUseCase: PushSupportedProtocolsUseCaseProtocol { private enum ProteusToMLSMigrationState: String { case disabled @@ -91,7 +97,7 @@ public struct PushSupportedProtocolsUseCase { private func remotelySupportedProtocols() async -> Set { let mlsFeature = try? await featureConfigRepository.fetchFeatureConfig( - with: .mls, + name: .mls, type: Feature.MLS.Config.self ) @@ -120,7 +126,7 @@ public struct PushSupportedProtocolsUseCase { private func currentMigrationState() async -> ProteusToMLSMigrationState { let mlsMigrationFeature = try? await featureConfigRepository.fetchFeatureConfig( - with: .mlsMigration, + name: .mlsMigration, type: Feature.MLSMigration.Config.self ) diff --git a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationCodeUpdateEventProcessor.swift b/WireDomain/Sources/WireDomainAPI/UseCases/IndividualToTeamMigrationUseCase.swift similarity index 54% rename from WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationCodeUpdateEventProcessor.swift rename to WireDomain/Sources/WireDomainAPI/UseCases/IndividualToTeamMigrationUseCase.swift index 03108b5a141..04db1a4525c 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/ConversationEventProcessor/ConversationCodeUpdateEventProcessor.swift +++ b/WireDomain/Sources/WireDomainAPI/UseCases/IndividualToTeamMigrationUseCase.swift @@ -16,25 +16,25 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import WireAPI - -/// Process conversation code update events. - -protocol ConversationCodeUpdateEventProcessorProtocol { - - /// Process a conversation code update event. - /// - /// - Parameter event: A conversation code update event. - - func processEvent(_ event: ConversationCodeUpdateEvent) async throws +import Foundation +public enum IndividualToTeamMigrationError: Error, Sendable { + case userAlreadyInTeam + case generic(any Error) } -struct ConversationCodeUpdateEventProcessor: ConversationCodeUpdateEventProcessorProtocol { +public struct IndividualToTeamMigrationResult: Sendable { + public let teamID: UUID + public let teamName: String - func processEvent(_: ConversationCodeUpdateEvent) async throws { - // TODO: [WPB-10165] - assertionFailure("not implemented yet") + public init(teamID: UUID, teamName: String) { + self.teamID = teamID + self.teamName = teamName } +} +// sourcery: AutoMockable +/// Sends a request to the backend to migrate the user to a team. +public protocol IndividualToTeamMigrationUseCase: Sendable { + func invoke(teamName: String) async throws -> IndividualToTeamMigrationResult } diff --git a/WireDomain/Sources/WireDomainSupport/Sourcery/config.yml b/WireDomain/Sources/WireDomainSupport/Sourcery/config.yml index b6f70e203bb..ba382d6f9b3 100644 --- a/WireDomain/Sources/WireDomainSupport/Sourcery/config.yml +++ b/WireDomain/Sources/WireDomainSupport/Sourcery/config.yml @@ -5,6 +5,5 @@ templates: output: ./generated args: - autoMockableTestableImports: ["WireDomain"] autoMockableImports: ["WireAPI", "WireDataModel"] - + autoMockableTestableImports: ["WireDomain"] diff --git a/WireDomain/Sources/WireDomainSupport/Sourcery/generated/AutoMockable.generated.swift b/WireDomain/Sources/WireDomainSupport/Sourcery/generated/AutoMockable.generated.swift index d328dbe9036..9452a00a877 100644 --- a/WireDomain/Sources/WireDomainSupport/Sourcery/generated/AutoMockable.generated.swift +++ b/WireDomain/Sources/WireDomainSupport/Sourcery/generated/AutoMockable.generated.swift @@ -24,15 +24,9 @@ // swiftlint:disable line_length // swiftlint:disable variable_name -public import Foundation -#if os(iOS) || os(tvOS) || os(watchOS) -public import UIKit -#elseif os(OSX) -public import AppKit -#endif -public import WireAPI -public import WireDataModel +import WireAPI +import WireDataModel @testable import WireDomain @@ -55,6 +49,35 @@ public import WireDataModel + +public class MockConnectionsLocalStoreProtocol: ConnectionsLocalStoreProtocol { + + // MARK: - Life cycle + + public init() {} + + + // MARK: - storeConnection + + public var storeConnection_Invocations: [ConnectionInfo] = [] + public var storeConnection_MockError: Error? + public var storeConnection_MockMethod: ((ConnectionInfo) async throws -> Void)? + + public func storeConnection(_ connectionInfo: ConnectionInfo) async throws { + storeConnection_Invocations.append(connectionInfo) + + if let error = storeConnection_MockError { + throw error + } + + guard let mock = storeConnection_MockMethod else { + fatalError("no mock for `storeConnection`") + } + + try await mock(connectionInfo) + } + +} public class MockConnectionsRepositoryProtocol: ConnectionsRepositoryProtocol { @@ -105,6 +128,55 @@ public class MockConnectionsRepositoryProtocol: ConnectionsRepositoryProtocol { } +public class MockConversationLabelsLocalStoreProtocol: ConversationLabelsLocalStoreProtocol { + + // MARK: - Life cycle + + public init() {} + + + // MARK: - storeLabel + + public var storeLabel_Invocations: [ConversationLabelInfo] = [] + public var storeLabel_MockError: Error? + public var storeLabel_MockMethod: ((ConversationLabelInfo) async throws -> Void)? + + public func storeLabel(_ conversationLabel: ConversationLabelInfo) async throws { + storeLabel_Invocations.append(conversationLabel) + + if let error = storeLabel_MockError { + throw error + } + + guard let mock = storeLabel_MockMethod else { + fatalError("no mock for `storeLabel`") + } + + try await mock(conversationLabel) + } + + // MARK: - deleteOldLabelsLocally + + public var deleteOldLabelsLocallyExcludedLabels_Invocations: [[ConversationLabelInfo]] = [] + public var deleteOldLabelsLocallyExcludedLabels_MockError: Error? + public var deleteOldLabelsLocallyExcludedLabels_MockMethod: (([ConversationLabelInfo]) async throws -> Void)? + + public func deleteOldLabelsLocally(excludedLabels: [ConversationLabelInfo]) async throws { + deleteOldLabelsLocallyExcludedLabels_Invocations.append(excludedLabels) + + if let error = deleteOldLabelsLocallyExcludedLabels_MockError { + throw error + } + + guard let mock = deleteOldLabelsLocallyExcludedLabels_MockMethod else { + fatalError("no mock for `deleteOldLabelsLocallyExcludedLabels`") + } + + try await mock(excludedLabels) + } + +} + public class MockConversationLabelsRepositoryProtocol: ConversationLabelsRepositoryProtocol { // MARK: - Life cycle @@ -181,47 +253,47 @@ public class MockConversationLocalStoreProtocol: ConversationLocalStoreProtocol // MARK: - storeConversation - public var storeConversationTimestampIsFederationEnabled_Invocations: [(conversation: WireAPI.Conversation, timestamp: Date, isFederationEnabled: Bool)] = [] - public var storeConversationTimestampIsFederationEnabled_MockMethod: ((WireAPI.Conversation, Date, Bool) async -> Void)? + public var storeConversationTimestampIsFederationEnabledIsMLSEnabled_Invocations: [(conversation: WireDomain.Conversation, timestamp: Date, isFederationEnabled: Bool, isMLSEnabled: Bool)] = [] + public var storeConversationTimestampIsFederationEnabledIsMLSEnabled_MockMethod: ((WireDomain.Conversation, Date, Bool, Bool) async -> Void)? - public func storeConversation(_ conversation: WireAPI.Conversation, timestamp: Date, isFederationEnabled: Bool) async { - storeConversationTimestampIsFederationEnabled_Invocations.append((conversation: conversation, timestamp: timestamp, isFederationEnabled: isFederationEnabled)) + public func storeConversation(_ conversation: WireDomain.Conversation, timestamp: Date, isFederationEnabled: Bool, isMLSEnabled: Bool) async { + storeConversationTimestampIsFederationEnabledIsMLSEnabled_Invocations.append((conversation: conversation, timestamp: timestamp, isFederationEnabled: isFederationEnabled, isMLSEnabled: isMLSEnabled)) - guard let mock = storeConversationTimestampIsFederationEnabled_MockMethod else { - fatalError("no mock for `storeConversationTimestampIsFederationEnabled`") + guard let mock = storeConversationTimestampIsFederationEnabledIsMLSEnabled_MockMethod else { + fatalError("no mock for `storeConversationTimestampIsFederationEnabledIsMLSEnabled`") } - await mock(conversation, timestamp, isFederationEnabled) + await mock(conversation, timestamp, isFederationEnabled, isMLSEnabled) } // MARK: - storeConversation - public var storeConversationNeedsBackendUpdateQualifiedId_Invocations: [(needsBackendUpdate: Bool, qualifiedId: WireAPI.QualifiedID)] = [] - public var storeConversationNeedsBackendUpdateQualifiedId_MockMethod: ((Bool, WireAPI.QualifiedID) async -> Void)? + public var storeConversationNeedsBackendUpdateConversationIDConversationDomain_Invocations: [(needsBackendUpdate: Bool, conversationID: UUID, conversationDomain: String)] = [] + public var storeConversationNeedsBackendUpdateConversationIDConversationDomain_MockMethod: ((Bool, UUID, String) async -> Void)? - public func storeConversation(needsBackendUpdate: Bool, qualifiedId: WireAPI.QualifiedID) async { - storeConversationNeedsBackendUpdateQualifiedId_Invocations.append((needsBackendUpdate: needsBackendUpdate, qualifiedId: qualifiedId)) + public func storeConversation(needsBackendUpdate: Bool, conversationID: UUID, conversationDomain: String) async { + storeConversationNeedsBackendUpdateConversationIDConversationDomain_Invocations.append((needsBackendUpdate: needsBackendUpdate, conversationID: conversationID, conversationDomain: conversationDomain)) - guard let mock = storeConversationNeedsBackendUpdateQualifiedId_MockMethod else { - fatalError("no mock for `storeConversationNeedsBackendUpdateQualifiedId`") + guard let mock = storeConversationNeedsBackendUpdateConversationIDConversationDomain_MockMethod else { + fatalError("no mock for `storeConversationNeedsBackendUpdateConversationIDConversationDomain`") } - await mock(needsBackendUpdate, qualifiedId) + await mock(needsBackendUpdate, conversationID, conversationDomain) } // MARK: - storeFailedConversation - public var storeFailedConversationWithQualifiedId_Invocations: [WireAPI.QualifiedID] = [] - public var storeFailedConversationWithQualifiedId_MockMethod: ((WireAPI.QualifiedID) async -> Void)? + public var storeFailedConversationConversationIDConversationDomain_Invocations: [(conversationID: UUID, conversationDomain: String)] = [] + public var storeFailedConversationConversationIDConversationDomain_MockMethod: ((UUID, String) async -> Void)? - public func storeFailedConversation(withQualifiedId qualifiedId: WireAPI.QualifiedID) async { - storeFailedConversationWithQualifiedId_Invocations.append(qualifiedId) + public func storeFailedConversation(conversationID: UUID, conversationDomain: String) async { + storeFailedConversationConversationIDConversationDomain_Invocations.append((conversationID: conversationID, conversationDomain: conversationDomain)) - guard let mock = storeFailedConversationWithQualifiedId_MockMethod else { - fatalError("no mock for `storeFailedConversationWithQualifiedId`") + guard let mock = storeFailedConversationConversationIDConversationDomain_MockMethod else { + fatalError("no mock for `storeFailedConversationConversationIDConversationDomain`") } - await mock(qualifiedId) + await mock(conversationID, conversationDomain) } // MARK: - fetchMLSConversation @@ -282,17 +354,22 @@ public class MockConversationLocalStoreProtocol: ConversationLocalStoreProtocol // MARK: - removeParticipantFromAllGroupConversations - public var removeParticipantFromAllGroupConversationsUserDate_Invocations: [(user: ZMUser, date: Date)] = [] - public var removeParticipantFromAllGroupConversationsUserDate_MockMethod: ((ZMUser, Date) async -> Void)? + public var removeParticipantFromAllGroupConversationsParticipantIDParticipantDomainDate_Invocations: [(participantID: UUID, participantDomain: String?, date: Date)] = [] + public var removeParticipantFromAllGroupConversationsParticipantIDParticipantDomainDate_MockError: Error? + public var removeParticipantFromAllGroupConversationsParticipantIDParticipantDomainDate_MockMethod: ((UUID, String?, Date) async throws -> Void)? - public func removeParticipantFromAllGroupConversations(user: ZMUser, date: Date) async { - removeParticipantFromAllGroupConversationsUserDate_Invocations.append((user: user, date: date)) + public func removeParticipantFromAllGroupConversations(participantID: UUID, participantDomain: String?, date: Date) async throws { + removeParticipantFromAllGroupConversationsParticipantIDParticipantDomainDate_Invocations.append((participantID: participantID, participantDomain: participantDomain, date: date)) - guard let mock = removeParticipantFromAllGroupConversationsUserDate_MockMethod else { - fatalError("no mock for `removeParticipantFromAllGroupConversationsUserDate`") + if let error = removeParticipantFromAllGroupConversationsParticipantIDParticipantDomainDate_MockError { + throw error } - await mock(user, date) + guard let mock = removeParticipantFromAllGroupConversationsParticipantIDParticipantDomainDate_MockMethod else { + fatalError("no mock for `removeParticipantFromAllGroupConversationsParticipantIDParticipantDomainDate`") + } + + try await mock(participantID, participantDomain, date) } // MARK: - addOrUpdateParticipant @@ -312,19 +389,19 @@ public class MockConversationLocalStoreProtocol: ConversationLocalStoreProtocol // MARK: - addParticipants - public var addParticipantsAddedByAtDateTo_Invocations: [(participants: [(id: UUID, domain: String?, role: String?)], sender: (id: UUID, domain: String?), date: Date, conversation: ZMConversation)] = [] - public var addParticipantsAddedByAtDateTo_MockError: Error? - public var addParticipantsAddedByAtDateTo_MockMethod: (([(id: UUID, domain: String?, role: String?)], (id: UUID, domain: String?), Date, ZMConversation) async throws -> Void)? + public var addParticipantsAddedByAtDateConversation_Invocations: [(participants: [(id: UUID, domain: String?, role: String?)], sender: (id: UUID, domain: String?), date: Date, conversation: (id: UUID, domain: String))] = [] + public var addParticipantsAddedByAtDateConversation_MockError: Error? + public var addParticipantsAddedByAtDateConversation_MockMethod: (([(id: UUID, domain: String?, role: String?)], (id: UUID, domain: String?), Date, (id: UUID, domain: String)) async throws -> Void)? - public func addParticipants(_ participants: [(id: UUID, domain: String?, role: String?)], addedBy sender: (id: UUID, domain: String?), atDate date: Date, to conversation: ZMConversation) async throws { - addParticipantsAddedByAtDateTo_Invocations.append((participants: participants, sender: sender, date: date, conversation: conversation)) + public func addParticipants(_ participants: [(id: UUID, domain: String?, role: String?)], addedBy sender: (id: UUID, domain: String?), atDate date: Date, conversation: (id: UUID, domain: String)) async throws { + addParticipantsAddedByAtDateConversation_Invocations.append((participants: participants, sender: sender, date: date, conversation: conversation)) - if let error = addParticipantsAddedByAtDateTo_MockError { + if let error = addParticipantsAddedByAtDateConversation_MockError { throw error } - guard let mock = addParticipantsAddedByAtDateTo_MockMethod else { - fatalError("no mock for `addParticipantsAddedByAtDateTo`") + guard let mock = addParticipantsAddedByAtDateConversation_MockMethod else { + fatalError("no mock for `addParticipantsAddedByAtDateConversation`") } try await mock(participants, sender, date, conversation) @@ -378,100 +455,67 @@ public class MockConversationLocalStoreProtocol: ConversationLocalStoreProtocol } } - // MARK: - addSystemMessage + // MARK: - storeConversation - public var addSystemMessageTo_Invocations: [(message: SystemMessage, conversation: ZMConversation)] = [] - public var addSystemMessageTo_MockMethod: ((SystemMessage, ZMConversation) async -> Void)? + public var storeConversationHasReadReceiptsEnabledFor_Invocations: [(hasReadReceiptsEnabled: Bool, conversation: ZMConversation)] = [] + public var storeConversationHasReadReceiptsEnabledFor_MockMethod: ((Bool, ZMConversation) async -> Void)? - public func addSystemMessage(_ message: SystemMessage, to conversation: ZMConversation) async { - addSystemMessageTo_Invocations.append((message: message, conversation: conversation)) + public func storeConversation(hasReadReceiptsEnabled: Bool, for conversation: ZMConversation) async { + storeConversationHasReadReceiptsEnabledFor_Invocations.append((hasReadReceiptsEnabled: hasReadReceiptsEnabled, conversation: conversation)) - guard let mock = addSystemMessageTo_MockMethod else { - fatalError("no mock for `addSystemMessageTo`") + guard let mock = storeConversationHasReadReceiptsEnabledFor_MockMethod else { + fatalError("no mock for `storeConversationHasReadReceiptsEnabledFor`") } - await mock(message, conversation) - } - - // MARK: - conversationMutedMessageTypes - - public var conversationMutedMessageTypes_Invocations: [ZMConversation] = [] - public var conversationMutedMessageTypes_MockMethod: ((ZMConversation) async -> MutedMessageTypes)? - public var conversationMutedMessageTypes_MockValue: MutedMessageTypes? - - public func conversationMutedMessageTypes(_ conversation: ZMConversation) async -> MutedMessageTypes { - conversationMutedMessageTypes_Invocations.append(conversation) - - if let mock = conversationMutedMessageTypes_MockMethod { - return await mock(conversation) - } else if let mock = conversationMutedMessageTypes_MockValue { - return mock - } else { - fatalError("no mock for `conversationMutedMessageTypes`") - } + await mock(hasReadReceiptsEnabled, conversation) } - // MARK: - storeConversation + // MARK: - removeParticipantsAndUpdateConversationState - public var storeConversationIsArchivedFor_Invocations: [(isArchived: Bool, conversation: ZMConversation)] = [] - public var storeConversationIsArchivedFor_MockMethod: ((Bool, ZMConversation) async -> Void)? + public var removeParticipantsAndUpdateConversationStateConversationUsersInitiatingUser_Invocations: [(conversation: ZMConversation, users: Set, initiatingUser: ZMUser)] = [] + public var removeParticipantsAndUpdateConversationStateConversationUsersInitiatingUser_MockMethod: ((ZMConversation, Set, ZMUser) async -> Void)? - public func storeConversation(isArchived: Bool, for conversation: ZMConversation) async { - storeConversationIsArchivedFor_Invocations.append((isArchived: isArchived, conversation: conversation)) + public func removeParticipantsAndUpdateConversationState(conversation: ZMConversation, users: Set, initiatingUser: ZMUser) async { + removeParticipantsAndUpdateConversationStateConversationUsersInitiatingUser_Invocations.append((conversation: conversation, users: users, initiatingUser: initiatingUser)) - guard let mock = storeConversationIsArchivedFor_MockMethod else { - fatalError("no mock for `storeConversationIsArchivedFor`") + guard let mock = removeParticipantsAndUpdateConversationStateConversationUsersInitiatingUser_MockMethod else { + fatalError("no mock for `removeParticipantsAndUpdateConversationStateConversationUsersInitiatingUser`") } - await mock(isArchived, conversation) + await mock(conversation, users, initiatingUser) } - // MARK: - isConversationArchived + // MARK: - conversationMessageDestructionTimeout - public var isConversationArchived_Invocations: [ZMConversation] = [] - public var isConversationArchived_MockMethod: ((ZMConversation) async -> Bool)? - public var isConversationArchived_MockValue: Bool? + public var conversationMessageDestructionTimeout_Invocations: [ZMConversation] = [] + public var conversationMessageDestructionTimeout_MockMethod: ((ZMConversation) async -> MessageDestructionTimeoutValue)? + public var conversationMessageDestructionTimeout_MockValue: MessageDestructionTimeoutValue? - public func isConversationArchived(_ conversation: ZMConversation) async -> Bool { - isConversationArchived_Invocations.append(conversation) + public func conversationMessageDestructionTimeout(_ conversation: ZMConversation) async -> MessageDestructionTimeoutValue { + conversationMessageDestructionTimeout_Invocations.append(conversation) - if let mock = isConversationArchived_MockMethod { + if let mock = conversationMessageDestructionTimeout_MockMethod { return await mock(conversation) - } else if let mock = isConversationArchived_MockValue { + } else if let mock = conversationMessageDestructionTimeout_MockValue { return mock } else { - fatalError("no mock for `isConversationArchived`") + fatalError("no mock for `conversationMessageDestructionTimeout`") } } // MARK: - storeConversation - public var storeConversationHasReadReceiptsEnabledFor_Invocations: [(hasReadReceiptsEnabled: Bool, conversation: ZMConversation)] = [] - public var storeConversationHasReadReceiptsEnabledFor_MockMethod: ((Bool, ZMConversation) async -> Void)? + public var storeConversationTimeoutValueFor_Invocations: [(timeoutValue: Double, conversation: ZMConversation)] = [] + public var storeConversationTimeoutValueFor_MockMethod: ((Double, ZMConversation) async -> Void)? - public func storeConversation(hasReadReceiptsEnabled: Bool, for conversation: ZMConversation) async { - storeConversationHasReadReceiptsEnabledFor_Invocations.append((hasReadReceiptsEnabled: hasReadReceiptsEnabled, conversation: conversation)) - - guard let mock = storeConversationHasReadReceiptsEnabledFor_MockMethod else { - fatalError("no mock for `storeConversationHasReadReceiptsEnabledFor`") - } - - await mock(hasReadReceiptsEnabled, conversation) - } - - // MARK: - removeParticipantsAndUpdateConversationState + public func storeConversation(timeoutValue: Double, for conversation: ZMConversation) async { + storeConversationTimeoutValueFor_Invocations.append((timeoutValue: timeoutValue, conversation: conversation)) - public var removeParticipantsAndUpdateConversationStateConversationUsersInitiatingUser_Invocations: [(conversation: ZMConversation, users: Set, initiatingUser: ZMUser)] = [] - public var removeParticipantsAndUpdateConversationStateConversationUsersInitiatingUser_MockMethod: ((ZMConversation, Set, ZMUser) async -> Void)? - - public func removeParticipantsAndUpdateConversationState(conversation: ZMConversation, users: Set, initiatingUser: ZMUser) async { - removeParticipantsAndUpdateConversationStateConversationUsersInitiatingUser_Invocations.append((conversation: conversation, users: users, initiatingUser: initiatingUser)) - - guard let mock = removeParticipantsAndUpdateConversationStateConversationUsersInitiatingUser_MockMethod else { - fatalError("no mock for `removeParticipantsAndUpdateConversationStateConversationUsersInitiatingUser`") + guard let mock = storeConversationTimeoutValueFor_MockMethod else { + fatalError("no mock for `storeConversationTimeoutValueFor`") } - await mock(conversation, users, initiatingUser) + await mock(timeoutValue, conversation) } // MARK: - fetchOrCreateRole @@ -594,6 +638,117 @@ public class MockConversationLocalStoreProtocol: ConversationLocalStoreProtocol } } + // MARK: - updateTypingUsers + + public var updateTypingUsersConversationIDUsersID_Invocations: [(conversationID: NSManagedObjectID, usersID: Set)] = [] + public var updateTypingUsersConversationIDUsersID_MockMethod: ((NSManagedObjectID, Set) async -> Void)? + + public func updateTypingUsers(conversationID: NSManagedObjectID, usersID: Set) async { + updateTypingUsersConversationIDUsersID_Invocations.append((conversationID: conversationID, usersID: usersID)) + + guard let mock = updateTypingUsersConversationIDUsersID_MockMethod else { + fatalError("no mock for `updateTypingUsersConversationIDUsersID`") + } + + await mock(conversationID, usersID) + } + + // MARK: - obtainPermanentIDs + + public var obtainPermanentIDsUserConversation_Invocations: [(user: ZMUser, conversation: ZMConversation)] = [] + public var obtainPermanentIDsUserConversation_MockMethod: ((ZMUser, ZMConversation) -> Void)? + + public func obtainPermanentIDs(user: ZMUser, conversation: ZMConversation) { + obtainPermanentIDsUserConversation_Invocations.append((user: user, conversation: conversation)) + + guard let mock = obtainPermanentIDsUserConversation_MockMethod else { + fatalError("no mock for `obtainPermanentIDsUserConversation`") + } + + mock(user, conversation) + } + + // MARK: - conversationName + + public var conversationNameConversation_Invocations: [ZMConversation] = [] + public var conversationNameConversation_MockMethod: ((ZMConversation) async -> String?)? + public var conversationNameConversation_MockValue: String?? + + public func conversationName(conversation: ZMConversation) async -> String? { + conversationNameConversation_Invocations.append(conversation) + + if let mock = conversationNameConversation_MockMethod { + return await mock(conversation) + } else if let mock = conversationNameConversation_MockValue { + return mock + } else { + fatalError("no mock for `conversationNameConversation`") + } + } + + // MARK: - storeConversation + + public var storeConversationNewNameConversation_Invocations: [(newName: String, conversation: ZMConversation)] = [] + public var storeConversationNewNameConversation_MockMethod: ((String, ZMConversation) async -> Void)? + + public func storeConversation(newName: String, conversation: ZMConversation) async { + storeConversationNewNameConversation_Invocations.append((newName: newName, conversation: conversation)) + + guard let mock = storeConversationNewNameConversation_MockMethod else { + fatalError("no mock for `storeConversationNewNameConversation`") + } + + await mock(newName, conversation) + } + + // MARK: - updateOrCreateMLSGroup + + public var updateOrCreateMLSGroupGroupID_Invocations: [MLSGroupID] = [] + public var updateOrCreateMLSGroupGroupID_MockMethod: ((MLSGroupID) async -> Void)? + + public func updateOrCreateMLSGroup(groupID: MLSGroupID) async { + updateOrCreateMLSGroupGroupID_Invocations.append(groupID) + + guard let mock = updateOrCreateMLSGroupGroupID_MockMethod else { + fatalError("no mock for `updateOrCreateMLSGroupGroupID`") + } + + await mock(groupID) + } + + // MARK: - storeMLSConversationEstablished + + public var storeMLSConversationEstablishedMlsGroupIDConversation_Invocations: [(mlsGroupID: MLSGroupID, conversation: ZMConversation)] = [] + public var storeMLSConversationEstablishedMlsGroupIDConversation_MockMethod: ((MLSGroupID, ZMConversation) async -> Void)? + + public func storeMLSConversationEstablished(mlsGroupID: MLSGroupID, conversation: ZMConversation) async { + storeMLSConversationEstablishedMlsGroupIDConversation_Invocations.append((mlsGroupID: mlsGroupID, conversation: conversation)) + + guard let mock = storeMLSConversationEstablishedMlsGroupIDConversation_MockMethod else { + fatalError("no mock for `storeMLSConversationEstablishedMlsGroupIDConversation`") + } + + await mock(mlsGroupID, conversation) + } + + // MARK: - fetchOtherUserIDInOneOnOneConversation + + public var fetchOtherUserIDInOneOnOneConversationConversation_Invocations: [ZMConversation] = [] + public var fetchOtherUserIDInOneOnOneConversationConversation_MockMethod: ((ZMConversation) async -> WireDataModel.QualifiedID?)? + public var fetchOtherUserIDInOneOnOneConversationConversation_MockValue: WireDataModel.QualifiedID?? + + public func fetchOtherUserIDInOneOnOneConversation(conversation: ZMConversation) async -> WireDataModel.QualifiedID? { + fetchOtherUserIDInOneOnOneConversationConversation_Invocations.append(conversation) + + if let mock = fetchOtherUserIDInOneOnOneConversationConversation_MockMethod { + return await mock(conversation) + } else if let mock = fetchOtherUserIDInOneOnOneConversationConversation_MockValue { + return mock + } else { + fatalError("no mock for `fetchOtherUserIDInOneOnOneConversationConversation`") + } + } + } public class MockConversationRepositoryProtocol: ConversationRepositoryProtocol { @@ -643,10 +798,10 @@ public class MockConversationRepositoryProtocol: ConversationRepositoryProtocol // MARK: - storeConversation - public var storeConversationTimestamp_Invocations: [(conversation: WireAPI.Conversation, timestamp: Date)] = [] - public var storeConversationTimestamp_MockMethod: ((WireAPI.Conversation, Date) async -> Void)? + public var storeConversationTimestamp_Invocations: [(conversation: WireDomain.Conversation, timestamp: Date)] = [] + public var storeConversationTimestamp_MockMethod: ((WireDomain.Conversation, Date) async -> Void)? - public func storeConversation(_ conversation: WireAPI.Conversation, timestamp: Date) async { + public func storeConversation(_ conversation: WireDomain.Conversation, timestamp: Date) async { storeConversationTimestamp_Invocations.append((conversation: conversation, timestamp: timestamp)) guard let mock = storeConversationTimestamp_MockMethod else { @@ -830,19 +985,57 @@ public class MockConversationRepositoryProtocol: ConversationRepositoryProtocol try await mock(userIDs, conversation, sender, date, reason) } - // MARK: - addSystemMessage + // MARK: - updateConversationName - public var addSystemMessageTo_Invocations: [(message: SystemMessage, conversation: ZMConversation)] = [] - public var addSystemMessageTo_MockMethod: ((SystemMessage, ZMConversation) async -> Void)? + public var updateConversationNameNewNameConversationIDConversationDomainSenderIDSenderDomainDate_Invocations: [(newName: String, conversationID: UUID, conversationDomain: String?, senderID: UUID, senderDomain: String?, date: Date)] = [] + public var updateConversationNameNewNameConversationIDConversationDomainSenderIDSenderDomainDate_MockMethod: ((String, UUID, String?, UUID, String?, Date) async -> Void)? - public func addSystemMessage(_ message: SystemMessage, to conversation: ZMConversation) async { - addSystemMessageTo_Invocations.append((message: message, conversation: conversation)) + public func updateConversationName(newName: String, conversationID: UUID, conversationDomain: String?, senderID: UUID, senderDomain: String?, date: Date) async { + updateConversationNameNewNameConversationIDConversationDomainSenderIDSenderDomainDate_Invocations.append((newName: newName, conversationID: conversationID, conversationDomain: conversationDomain, senderID: senderID, senderDomain: senderDomain, date: date)) - guard let mock = addSystemMessageTo_MockMethod else { - fatalError("no mock for `addSystemMessageTo`") + guard let mock = updateConversationNameNewNameConversationIDConversationDomainSenderIDSenderDomainDate_MockMethod else { + fatalError("no mock for `updateConversationNameNewNameConversationIDConversationDomainSenderIDSenderDomainDate`") } - await mock(message, conversation) + await mock(newName, conversationID, conversationDomain, senderID, senderDomain, date) + } + + // MARK: - updateTypingUsers + + public var updateTypingUsers_Invocations: [[ConversationTypingUsersInfo]] = [] + public var updateTypingUsers_MockMethod: (([ConversationTypingUsersInfo]) async -> Void)? + + public func updateTypingUsers(_ typingUsersInfo: [ConversationTypingUsersInfo]) async { + updateTypingUsers_Invocations.append(typingUsersInfo) + + guard let mock = updateTypingUsers_MockMethod else { + fatalError("no mock for `updateTypingUsers`") + } + + await mock(typingUsersInfo) + } + + // MARK: - fetchConversationGuestLink + + public var fetchConversationGuestLinkConversationID_Invocations: [String] = [] + public var fetchConversationGuestLinkConversationID_MockError: Error? + public var fetchConversationGuestLinkConversationID_MockMethod: ((String) async throws -> String?)? + public var fetchConversationGuestLinkConversationID_MockValue: String?? + + public func fetchConversationGuestLink(conversationID: String) async throws -> String? { + fetchConversationGuestLinkConversationID_Invocations.append(conversationID) + + if let error = fetchConversationGuestLinkConversationID_MockError { + throw error + } + + if let mock = fetchConversationGuestLinkConversationID_MockMethod { + return try await mock(conversationID) + } else if let mock = fetchConversationGuestLinkConversationID_MockValue { + return mock + } else { + fatalError("no mock for `fetchConversationGuestLinkConversationID`") + } } } @@ -902,6 +1095,86 @@ public class MockOneOnOneResolverProtocol: OneOnOneResolverProtocol { public init() {} + // MARK: - resolveOneOnOneConversation + + public var resolveOneOnOneConversationWith_Invocations: [WireDataModel.QualifiedID] = [] + public var resolveOneOnOneConversationWith_MockError: Error? + public var resolveOneOnOneConversationWith_MockMethod: ((WireDataModel.QualifiedID) async throws -> Void)? + + public func resolveOneOnOneConversation(with userID: WireDataModel.QualifiedID) async throws { + resolveOneOnOneConversationWith_Invocations.append(userID) + + if let error = resolveOneOnOneConversationWith_MockError { + throw error + } + + guard let mock = resolveOneOnOneConversationWith_MockMethod else { + fatalError("no mock for `resolveOneOnOneConversationWith`") + } + + try await mock(userID) + } + + // MARK: - resolveAllOneOnOneConversations + + public var resolveAllOneOnOneConversations_Invocations: [Void] = [] + public var resolveAllOneOnOneConversations_MockError: Error? + public var resolveAllOneOnOneConversations_MockMethod: (() async throws -> Void)? + + public func resolveAllOneOnOneConversations() async throws { + resolveAllOneOnOneConversations_Invocations.append(()) + + if let error = resolveAllOneOnOneConversations_MockError { + throw error + } + + guard let mock = resolveAllOneOnOneConversations_MockMethod else { + fatalError("no mock for `resolveAllOneOnOneConversations`") + } + + try await mock() + } + +} + +class MockProteusMessageDecryptorProtocol: ProteusMessageDecryptorProtocol { + + // MARK: - Life cycle + + + + // MARK: - decryptedEventData + + var decryptedEventDataFrom_Invocations: [ConversationProteusMessageAddEvent] = [] + var decryptedEventDataFrom_MockError: Error? + var decryptedEventDataFrom_MockMethod: ((ConversationProteusMessageAddEvent) async throws -> ConversationProteusMessageAddEvent)? + var decryptedEventDataFrom_MockValue: ConversationProteusMessageAddEvent? + + func decryptedEventData(from eventData: ConversationProteusMessageAddEvent) async throws -> ConversationProteusMessageAddEvent { + decryptedEventDataFrom_Invocations.append(eventData) + + if let error = decryptedEventDataFrom_MockError { + throw error + } + + if let mock = decryptedEventDataFrom_MockMethod { + return try await mock(eventData) + } else if let mock = decryptedEventDataFrom_MockValue { + return mock + } else { + fatalError("no mock for `decryptedEventDataFrom`") + } + } + +} + +public class MockPushSupportedProtocolsUseCaseProtocol: PushSupportedProtocolsUseCaseProtocol { + + // MARK: - Life cycle + + public init() {} + + // MARK: - invoke public var invoke_Invocations: [Void] = [] @@ -924,59 +1197,212 @@ public class MockOneOnOneResolverProtocol: OneOnOneResolverProtocol { } -class MockProteusMessageDecryptorProtocol: ProteusMessageDecryptorProtocol { +public class MockSelfUserProviderProtocol: SelfUserProviderProtocol { + + // MARK: - Life cycle + + public init() {} + + + // MARK: - fetchSelfUser + + public var fetchSelfUser_Invocations: [Void] = [] + public var fetchSelfUser_MockMethod: (() -> ZMUser)? + public var fetchSelfUser_MockValue: ZMUser? + + public func fetchSelfUser() -> ZMUser { + fetchSelfUser_Invocations.append(()) + + if let mock = fetchSelfUser_MockMethod { + return mock() + } else if let mock = fetchSelfUser_MockValue { + return mock + } else { + fatalError("no mock for `fetchSelfUser`") + } + } + +} + +public class MockTeamLocalStoreProtocol: TeamLocalStoreProtocol { // MARK: - Life cycle + public init() {} + + + // MARK: - fetchMember + + public var fetchMemberId_Invocations: [UUID] = [] + public var fetchMemberId_MockMethod: ((UUID) async -> Member?)? + public var fetchMemberId_MockValue: Member?? + + public func fetchMember(id: UUID) async -> Member? { + fetchMemberId_Invocations.append(id) + + if let mock = fetchMemberId_MockMethod { + return await mock(id) + } else if let mock = fetchMemberId_MockValue { + return mock + } else { + fatalError("no mock for `fetchMemberId`") + } + } + + // MARK: - selfUserID + + public var selfUserID_Invocations: [Void] = [] + public var selfUserID_MockMethod: (() async -> UUID)? + public var selfUserID_MockValue: UUID? + + public func selfUserID() async -> UUID { + selfUserID_Invocations.append(()) + + if let mock = selfUserID_MockMethod { + return await mock() + } else if let mock = selfUserID_MockValue { + return mock + } else { + fatalError("no mock for `selfUserID`") + } + } + + // MARK: - userMembership + + public var userMembershipUser_Invocations: [ZMUser] = [] + public var userMembershipUser_MockMethod: ((ZMUser) async -> Member?)? + public var userMembershipUser_MockValue: Member?? + + public func userMembership(user: ZMUser) async -> Member? { + userMembershipUser_Invocations.append(user) + + if let mock = userMembershipUser_MockMethod { + return await mock(user) + } else if let mock = userMembershipUser_MockValue { + return mock + } else { + fatalError("no mock for `userMembershipUser`") + } + } + + // MARK: - userDomain + + public var userDomainUser_Invocations: [ZMUser] = [] + public var userDomainUser_MockMethod: ((ZMUser) async -> String?)? + public var userDomainUser_MockValue: String?? + + public func userDomain(user: ZMUser) async -> String? { + userDomainUser_Invocations.append(user) + + if let mock = userDomainUser_MockMethod { + return await mock(user) + } else if let mock = userDomainUser_MockValue { + return mock + } else { + fatalError("no mock for `userDomainUser`") + } + } + + // MARK: - deleteMember + + public var deleteMember_Invocations: [Member] = [] + public var deleteMember_MockMethod: ((Member) async -> Void)? + + public func deleteMember(_ member: Member) async { + deleteMember_Invocations.append(member) + + guard let mock = deleteMember_MockMethod else { + fatalError("no mock for `deleteMember`") + } + + await mock(member) + } + + // MARK: - storeMember + + public var storeMemberNeedsBackendUpdateMember_Invocations: [(needsBackendUpdate: Bool, member: Member)] = [] + public var storeMemberNeedsBackendUpdateMember_MockMethod: ((Bool, Member) async -> Void)? + + public func storeMember(needsBackendUpdate: Bool, member: Member) async { + storeMemberNeedsBackendUpdateMember_Invocations.append((needsBackendUpdate: needsBackendUpdate, member: member)) + + guard let mock = storeMemberNeedsBackendUpdateMember_MockMethod else { + fatalError("no mock for `storeMemberNeedsBackendUpdateMember`") + } + + await mock(needsBackendUpdate, member) + } + + // MARK: - storeTeam + + public var storeTeamIdNameCreatorIDLogoIDLogoKey_Invocations: [(id: UUID, name: String, creatorID: UUID, logoID: String?, logoKey: String?)] = [] + public var storeTeamIdNameCreatorIDLogoIDLogoKey_MockMethod: ((UUID, String, UUID, String?, String?) async -> Void)? + + public func storeTeam(id: UUID, name: String, creatorID: UUID, logoID: String?, logoKey: String?) async { + storeTeamIdNameCreatorIDLogoIDLogoKey_Invocations.append((id: id, name: name, creatorID: creatorID, logoID: logoID, logoKey: logoKey)) + + guard let mock = storeTeamIdNameCreatorIDLogoIDLogoKey_MockMethod else { + fatalError("no mock for `storeTeamIdNameCreatorIDLogoIDLogoKey`") + } + await mock(id, name, creatorID, logoID, logoKey) + } - // MARK: - decryptedEventData + // MARK: - storeTeamRoles - var decryptedEventDataFrom_Invocations: [ConversationProteusMessageAddEvent] = [] - var decryptedEventDataFrom_MockError: Error? - var decryptedEventDataFrom_MockMethod: ((ConversationProteusMessageAddEvent) async throws -> ConversationProteusMessageAddEvent)? - var decryptedEventDataFrom_MockValue: ConversationProteusMessageAddEvent? + public var storeTeamRolesSelfTeamIDTeamRolesInfo_Invocations: [(selfTeamID: UUID, teamRolesInfo: [TeamRoleInfo])] = [] + public var storeTeamRolesSelfTeamIDTeamRolesInfo_MockError: Error? + public var storeTeamRolesSelfTeamIDTeamRolesInfo_MockMethod: ((UUID, [TeamRoleInfo]) async throws -> Void)? - func decryptedEventData(from eventData: ConversationProteusMessageAddEvent) async throws -> ConversationProteusMessageAddEvent { - decryptedEventDataFrom_Invocations.append(eventData) + public func storeTeamRoles(selfTeamID: UUID, teamRolesInfo: [TeamRoleInfo]) async throws { + storeTeamRolesSelfTeamIDTeamRolesInfo_Invocations.append((selfTeamID: selfTeamID, teamRolesInfo: teamRolesInfo)) - if let error = decryptedEventDataFrom_MockError { + if let error = storeTeamRolesSelfTeamIDTeamRolesInfo_MockError { throw error } - if let mock = decryptedEventDataFrom_MockMethod { - return try await mock(eventData) - } else if let mock = decryptedEventDataFrom_MockValue { - return mock - } else { - fatalError("no mock for `decryptedEventDataFrom`") + guard let mock = storeTeamRolesSelfTeamIDTeamRolesInfo_MockMethod else { + fatalError("no mock for `storeTeamRolesSelfTeamIDTeamRolesInfo`") } + + try await mock(selfTeamID, teamRolesInfo) } -} + // MARK: - storeTeamMembers -public class MockSelfUserProviderProtocol: SelfUserProviderProtocol { + public var storeTeamMembersSelfTeamIDTeamMembersInfo_Invocations: [(selfTeamID: UUID, teamMembersInfo: [TeamMemberInfo])] = [] + public var storeTeamMembersSelfTeamIDTeamMembersInfo_MockError: Error? + public var storeTeamMembersSelfTeamIDTeamMembersInfo_MockMethod: ((UUID, [TeamMemberInfo]) async throws -> Void)? - // MARK: - Life cycle + public func storeTeamMembers(selfTeamID: UUID, teamMembersInfo: [TeamMemberInfo]) async throws { + storeTeamMembersSelfTeamIDTeamMembersInfo_Invocations.append((selfTeamID: selfTeamID, teamMembersInfo: teamMembersInfo)) - public init() {} + if let error = storeTeamMembersSelfTeamIDTeamMembersInfo_MockError { + throw error + } + guard let mock = storeTeamMembersSelfTeamIDTeamMembersInfo_MockMethod else { + fatalError("no mock for `storeTeamMembersSelfTeamIDTeamMembersInfo`") + } - // MARK: - fetchSelfUser + try await mock(selfTeamID, teamMembersInfo) + } - public var fetchSelfUser_Invocations: [Void] = [] - public var fetchSelfUser_MockMethod: (() -> ZMUser)? - public var fetchSelfUser_MockValue: ZMUser? + // MARK: - selfUserInfo - public func fetchSelfUser() -> ZMUser { - fetchSelfUser_Invocations.append(()) + public var selfUserInfo_Invocations: [Void] = [] + public var selfUserInfo_MockMethod: (() async -> (id: UUID, clientId: String?))? + public var selfUserInfo_MockValue: (id: UUID, clientId: String?)? - if let mock = fetchSelfUser_MockMethod { - return mock() - } else if let mock = fetchSelfUser_MockValue { + public func selfUserInfo() async -> (id: UUID, clientId: String?) { + selfUserInfo_Invocations.append(()) + + if let mock = selfUserInfo_MockMethod { + return await mock() + } else if let mock = selfUserInfo_MockValue { return mock } else { - fatalError("no mock for `fetchSelfUser`") + fatalError("no mock for `selfUserInfo`") } } @@ -1049,47 +1475,47 @@ public class MockTeamRepositoryProtocol: TeamRepositoryProtocol { try await mock() } - // MARK: - fetchSelfLegalholdStatus + // MARK: - fetchSelfLegalholdInfo - public var fetchSelfLegalholdStatus_Invocations: [Void] = [] - public var fetchSelfLegalholdStatus_MockError: Error? - public var fetchSelfLegalholdStatus_MockMethod: (() async throws -> LegalholdStatus)? - public var fetchSelfLegalholdStatus_MockValue: LegalholdStatus? + public var fetchSelfLegalholdInfo_Invocations: [Void] = [] + public var fetchSelfLegalholdInfo_MockError: Error? + public var fetchSelfLegalholdInfo_MockMethod: (() async throws -> TeamMemberLegalholdInfo)? + public var fetchSelfLegalholdInfo_MockValue: TeamMemberLegalholdInfo? - public func fetchSelfLegalholdStatus() async throws -> LegalholdStatus { - fetchSelfLegalholdStatus_Invocations.append(()) + public func fetchSelfLegalholdInfo() async throws -> TeamMemberLegalholdInfo { + fetchSelfLegalholdInfo_Invocations.append(()) - if let error = fetchSelfLegalholdStatus_MockError { + if let error = fetchSelfLegalholdInfo_MockError { throw error } - if let mock = fetchSelfLegalholdStatus_MockMethod { + if let mock = fetchSelfLegalholdInfo_MockMethod { return try await mock() - } else if let mock = fetchSelfLegalholdStatus_MockValue { + } else if let mock = fetchSelfLegalholdInfo_MockValue { return mock } else { - fatalError("no mock for `fetchSelfLegalholdStatus`") + fatalError("no mock for `fetchSelfLegalholdInfo`") } } // MARK: - deleteMembership - public var deleteMembershipForDomainAt_Invocations: [(userID: UUID, domain: String?, time: Date)] = [] - public var deleteMembershipForDomainAt_MockError: Error? - public var deleteMembershipForDomainAt_MockMethod: ((UUID, String?, Date) async throws -> Void)? + public var deleteMembershipUserIDDomainDate_Invocations: [(userID: UUID, domain: String?, date: Date)] = [] + public var deleteMembershipUserIDDomainDate_MockError: Error? + public var deleteMembershipUserIDDomainDate_MockMethod: ((UUID, String?, Date) async throws -> Void)? - public func deleteMembership(for userID: UUID, domain: String?, at time: Date) async throws { - deleteMembershipForDomainAt_Invocations.append((userID: userID, domain: domain, time: time)) + public func deleteMembership(userID: UUID, domain: String?, date: Date) async throws { + deleteMembershipUserIDDomainDate_Invocations.append((userID: userID, domain: domain, date: date)) - if let error = deleteMembershipForDomainAt_MockError { + if let error = deleteMembershipUserIDDomainDate_MockError { throw error } - guard let mock = deleteMembershipForDomainAt_MockMethod else { - fatalError("no mock for `deleteMembershipForDomainAt`") + guard let mock = deleteMembershipUserIDDomainDate_MockMethod else { + fatalError("no mock for `deleteMembershipUserIDDomainDate`") } - try await mock(userID, domain, time) + try await mock(userID, domain, date) } // MARK: - storeTeamMemberNeedsBackendUpdate @@ -1112,6 +1538,26 @@ public class MockTeamRepositoryProtocol: TeamRepositoryProtocol { try await mock(membershipID) } + // MARK: - pullSelfLegalholdInfo + + public var pullSelfLegalholdInfo_Invocations: [Void] = [] + public var pullSelfLegalholdInfo_MockError: Error? + public var pullSelfLegalholdInfo_MockMethod: (() async throws -> Void)? + + public func pullSelfLegalholdInfo() async throws { + pullSelfLegalholdInfo_Invocations.append(()) + + if let error = pullSelfLegalholdInfo_MockError { + throw error + } + + guard let mock = pullSelfLegalholdInfo_MockMethod else { + fatalError("no mock for `pullSelfLegalholdInfo`") + } + + try await mock() + } + } class MockUpdateEventDecryptorProtocol: UpdateEventDecryptorProtocol { @@ -1173,6 +1619,133 @@ class MockUpdateEventProcessorProtocol: UpdateEventProcessorProtocol { } +class MockUpdateEventsLocalStoreProtocol: UpdateEventsLocalStoreProtocol { + + // MARK: - Life cycle + + + + // MARK: - lastEventID + + var lastEventID_Invocations: [Void] = [] + var lastEventID_MockMethod: (() -> UUID?)? + var lastEventID_MockValue: UUID?? + + func lastEventID() -> UUID? { + lastEventID_Invocations.append(()) + + if let mock = lastEventID_MockMethod { + return mock() + } else if let mock = lastEventID_MockValue { + return mock + } else { + fatalError("no mock for `lastEventID`") + } + } + + // MARK: - storeLastEventID + + var storeLastEventIDId_Invocations: [UUID] = [] + var storeLastEventIDId_MockMethod: ((UUID) -> Void)? + + func storeLastEventID(id: UUID) { + storeLastEventIDId_Invocations.append(id) + + guard let mock = storeLastEventIDId_MockMethod else { + fatalError("no mock for `storeLastEventIDId`") + } + + mock(id) + } + + // MARK: - indexOfLastEventEnvelope + + var indexOfLastEventEnvelope_Invocations: [Void] = [] + var indexOfLastEventEnvelope_MockError: Error? + var indexOfLastEventEnvelope_MockMethod: (() async throws -> Int64)? + var indexOfLastEventEnvelope_MockValue: Int64? + + func indexOfLastEventEnvelope() async throws -> Int64 { + indexOfLastEventEnvelope_Invocations.append(()) + + if let error = indexOfLastEventEnvelope_MockError { + throw error + } + + if let mock = indexOfLastEventEnvelope_MockMethod { + return try await mock() + } else if let mock = indexOfLastEventEnvelope_MockValue { + return mock + } else { + fatalError("no mock for `indexOfLastEventEnvelope`") + } + } + + // MARK: - persistEventEnvelope + + var persistEventEnvelopeIndex_Invocations: [(data: Data, index: Int64)] = [] + var persistEventEnvelopeIndex_MockError: Error? + var persistEventEnvelopeIndex_MockMethod: ((Data, Int64) async throws -> Void)? + + func persistEventEnvelope(_ data: Data, index: Int64) async throws { + persistEventEnvelopeIndex_Invocations.append((data: data, index: index)) + + if let error = persistEventEnvelopeIndex_MockError { + throw error + } + + guard let mock = persistEventEnvelopeIndex_MockMethod else { + fatalError("no mock for `persistEventEnvelopeIndex`") + } + + try await mock(data, index) + } + + // MARK: - fetchStoredEventEnvelopePayloads + + var fetchStoredEventEnvelopePayloadsLimit_Invocations: [UInt] = [] + var fetchStoredEventEnvelopePayloadsLimit_MockError: Error? + var fetchStoredEventEnvelopePayloadsLimit_MockMethod: ((UInt) async throws -> [Data])? + var fetchStoredEventEnvelopePayloadsLimit_MockValue: [Data]? + + func fetchStoredEventEnvelopePayloads(limit: UInt) async throws -> [Data] { + fetchStoredEventEnvelopePayloadsLimit_Invocations.append(limit) + + if let error = fetchStoredEventEnvelopePayloadsLimit_MockError { + throw error + } + + if let mock = fetchStoredEventEnvelopePayloadsLimit_MockMethod { + return try await mock(limit) + } else if let mock = fetchStoredEventEnvelopePayloadsLimit_MockValue { + return mock + } else { + fatalError("no mock for `fetchStoredEventEnvelopePayloadsLimit`") + } + } + + // MARK: - deleteNextPendingEvents + + var deleteNextPendingEventsLimit_Invocations: [UInt] = [] + var deleteNextPendingEventsLimit_MockError: Error? + var deleteNextPendingEventsLimit_MockMethod: ((UInt) async throws -> Void)? + + func deleteNextPendingEvents(limit: UInt) async throws { + deleteNextPendingEventsLimit_Invocations.append(limit) + + if let error = deleteNextPendingEventsLimit_MockError { + throw error + } + + guard let mock = deleteNextPendingEventsLimit_MockMethod else { + fatalError("no mock for `deleteNextPendingEventsLimit`") + } + + try await mock(limit) + } + +} + class MockUpdateEventsRepositoryProtocol: UpdateEventsRepositoryProtocol { // MARK: - Life cycle @@ -1317,6 +1890,99 @@ class MockUpdateEventsRepositoryProtocol: UpdateEventsRepositoryProtocol { } +public class MockUserClientsLocalStoreProtocol: UserClientsLocalStoreProtocol { + + // MARK: - Life cycle + + public init() {} + + + // MARK: - fetchOrCreateClient + + public var fetchOrCreateClientId_Invocations: [String] = [] + public var fetchOrCreateClientId_MockMethod: ((String) async -> (client: WireDataModel.UserClient, isNew: Bool))? + public var fetchOrCreateClientId_MockValue: (client: WireDataModel.UserClient, isNew: Bool)? + + public func fetchOrCreateClient(id: String) async -> (client: WireDataModel.UserClient, isNew: Bool) { + fetchOrCreateClientId_Invocations.append(id) + + if let mock = fetchOrCreateClientId_MockMethod { + return await mock(id) + } else if let mock = fetchOrCreateClientId_MockValue { + return mock + } else { + fatalError("no mock for `fetchOrCreateClientId`") + } + } + + // MARK: - deletedSelfClients + + public var deletedSelfClientsNewClients_Invocations: [[String]] = [] + public var deletedSelfClientsNewClients_MockMethod: (([String]) async -> [String])? + public var deletedSelfClientsNewClients_MockValue: [String]? + + public func deletedSelfClients(newClients: [String]) async -> [String] { + deletedSelfClientsNewClients_Invocations.append(newClients) + + if let mock = deletedSelfClientsNewClients_MockMethod { + return await mock(newClients) + } else if let mock = deletedSelfClientsNewClients_MockValue { + return mock + } else { + fatalError("no mock for `deletedSelfClientsNewClients`") + } + } + + // MARK: - deleteClient + + public var deleteClientId_Invocations: [String] = [] + public var deleteClientId_MockMethod: ((String) async -> Void)? + + public func deleteClient(id: String) async { + deleteClientId_Invocations.append(id) + + guard let mock = deleteClientId_MockMethod else { + fatalError("no mock for `deleteClientId`") + } + + await mock(id) + } + + // MARK: - updateClient + + public var updateClientIdIsNewClientUserClientInfo_Invocations: [(id: String, isNewClient: Bool, userClientInfo: UserClientInfo)] = [] + public var updateClientIdIsNewClientUserClientInfo_MockMethod: ((String, Bool, UserClientInfo) async -> Void)? + + public func updateClient(id: String, isNewClient: Bool, userClientInfo: UserClientInfo) async { + updateClientIdIsNewClientUserClientInfo_Invocations.append((id: id, isNewClient: isNewClient, userClientInfo: userClientInfo)) + + guard let mock = updateClientIdIsNewClientUserClientInfo_MockMethod else { + fatalError("no mock for `updateClientIdIsNewClientUserClientInfo`") + } + + await mock(id, isNewClient, userClientInfo) + } + + // MARK: - allSelfUserClientsAreActiveMLSClients + + public var allSelfUserClientsAreActiveMLSClients_Invocations: [Void] = [] + public var allSelfUserClientsAreActiveMLSClients_MockMethod: (() async -> Bool)? + public var allSelfUserClientsAreActiveMLSClients_MockValue: Bool? + + public func allSelfUserClientsAreActiveMLSClients() async -> Bool { + allSelfUserClientsAreActiveMLSClients_Invocations.append(()) + + if let mock = allSelfUserClientsAreActiveMLSClients_MockMethod { + return await mock() + } else if let mock = allSelfUserClientsAreActiveMLSClients_MockValue { + return mock + } else { + fatalError("no mock for `allSelfUserClientsAreActiveMLSClients`") + } + } + +} + public class MockUserClientsRepositoryProtocol: UserClientsRepositoryProtocol { // MARK: - Life cycle @@ -1346,42 +2012,42 @@ public class MockUserClientsRepositoryProtocol: UserClientsRepositoryProtocol { // MARK: - fetchOrCreateClient - public var fetchOrCreateClientWith_Invocations: [String] = [] - public var fetchOrCreateClientWith_MockError: Error? - public var fetchOrCreateClientWith_MockMethod: ((String) async throws -> (client: WireDataModel.UserClient, isNew: Bool))? - public var fetchOrCreateClientWith_MockValue: (client: WireDataModel.UserClient, isNew: Bool)? + public var fetchOrCreateClientId_Invocations: [String] = [] + public var fetchOrCreateClientId_MockError: Error? + public var fetchOrCreateClientId_MockMethod: ((String) async throws -> (client: WireDataModel.UserClient, isNew: Bool))? + public var fetchOrCreateClientId_MockValue: (client: WireDataModel.UserClient, isNew: Bool)? - public func fetchOrCreateClient(with id: String) async throws -> (client: WireDataModel.UserClient, isNew: Bool) { - fetchOrCreateClientWith_Invocations.append(id) + public func fetchOrCreateClient(id: String) async throws -> (client: WireDataModel.UserClient, isNew: Bool) { + fetchOrCreateClientId_Invocations.append(id) - if let error = fetchOrCreateClientWith_MockError { + if let error = fetchOrCreateClientId_MockError { throw error } - if let mock = fetchOrCreateClientWith_MockMethod { + if let mock = fetchOrCreateClientId_MockMethod { return try await mock(id) - } else if let mock = fetchOrCreateClientWith_MockValue { + } else if let mock = fetchOrCreateClientId_MockValue { return mock } else { - fatalError("no mock for `fetchOrCreateClientWith`") + fatalError("no mock for `fetchOrCreateClientId`") } } // MARK: - updateClient - public var updateClientWithFromIsNewClient_Invocations: [(id: String, remoteClient: WireAPI.SelfUserClient, isNewClient: Bool)] = [] - public var updateClientWithFromIsNewClient_MockError: Error? - public var updateClientWithFromIsNewClient_MockMethod: ((String, WireAPI.SelfUserClient, Bool) async throws -> Void)? + public var updateClientIdFromIsNewClient_Invocations: [(id: String, remoteClient: WireAPI.SelfUserClient, isNewClient: Bool)] = [] + public var updateClientIdFromIsNewClient_MockError: Error? + public var updateClientIdFromIsNewClient_MockMethod: ((String, WireAPI.SelfUserClient, Bool) async throws -> Void)? - public func updateClient(with id: String, from remoteClient: WireAPI.SelfUserClient, isNewClient: Bool) async throws { - updateClientWithFromIsNewClient_Invocations.append((id: id, remoteClient: remoteClient, isNewClient: isNewClient)) + public func updateClient(id: String, from remoteClient: WireAPI.SelfUserClient, isNewClient: Bool) async throws { + updateClientIdFromIsNewClient_Invocations.append((id: id, remoteClient: remoteClient, isNewClient: isNewClient)) - if let error = updateClientWithFromIsNewClient_MockError { + if let error = updateClientIdFromIsNewClient_MockError { throw error } - guard let mock = updateClientWithFromIsNewClient_MockMethod else { - fatalError("no mock for `updateClientWithFromIsNewClient`") + guard let mock = updateClientIdFromIsNewClient_MockMethod else { + fatalError("no mock for `updateClientIdFromIsNewClient`") } try await mock(id, remoteClient, isNewClient) @@ -1389,14 +2055,14 @@ public class MockUserClientsRepositoryProtocol: UserClientsRepositoryProtocol { // MARK: - deleteClient - public var deleteClientWith_Invocations: [String] = [] - public var deleteClientWith_MockMethod: ((String) async -> Void)? + public var deleteClientId_Invocations: [String] = [] + public var deleteClientId_MockMethod: ((String) async -> Void)? - public func deleteClient(with id: String) async { - deleteClientWith_Invocations.append(id) + public func deleteClient(id: String) async { + deleteClientId_Invocations.append(id) - guard let mock = deleteClientWith_MockMethod else { - fatalError("no mock for `deleteClientWith`") + guard let mock = deleteClientId_MockMethod else { + fatalError("no mock for `deleteClientId`") } await mock(id) @@ -1488,57 +2154,37 @@ public class MockUserLocalStoreProtocol: UserLocalStoreProtocol { } } - // MARK: - deletePushToken - - public var deletePushToken_Invocations: [Void] = [] - public var deletePushToken_MockMethod: (() -> Void)? - - public func deletePushToken() { - deletePushToken_Invocations.append(()) - - guard let mock = deletePushToken_MockMethod else { - fatalError("no mock for `deletePushToken`") - } - - mock() - } - - // MARK: - fetchOrCreateUserClient + // MARK: - fetchOrCreateUsers - public var fetchOrCreateUserClientId_Invocations: [String] = [] - public var fetchOrCreateUserClientId_MockMethod: ((String) async -> (client: WireDataModel.UserClient, isNew: Bool))? - public var fetchOrCreateUserClientId_MockValue: (client: WireDataModel.UserClient, isNew: Bool)? + public var fetchOrCreateUsersUserIDs_Invocations: [[(id: UUID, domain: String?)]] = [] + public var fetchOrCreateUsersUserIDs_MockMethod: (([(id: UUID, domain: String?)]) async -> Set)? + public var fetchOrCreateUsersUserIDs_MockValue: Set? - public func fetchOrCreateUserClient(id: String) async -> (client: WireDataModel.UserClient, isNew: Bool) { - fetchOrCreateUserClientId_Invocations.append(id) + public func fetchOrCreateUsers(userIDs: [(id: UUID, domain: String?)]) async -> Set { + fetchOrCreateUsersUserIDs_Invocations.append(userIDs) - if let mock = fetchOrCreateUserClientId_MockMethod { - return await mock(id) - } else if let mock = fetchOrCreateUserClientId_MockValue { + if let mock = fetchOrCreateUsersUserIDs_MockMethod { + return await mock(userIDs) + } else if let mock = fetchOrCreateUsersUserIDs_MockValue { return mock } else { - fatalError("no mock for `fetchOrCreateUserClientId`") + fatalError("no mock for `fetchOrCreateUsersUserIDs`") } } - // MARK: - updateUserClient - - public var updateUserClientFromIsNewClient_Invocations: [(localClient: WireDataModel.UserClient, remoteClient: WireAPI.SelfUserClient, isNewClient: Bool)] = [] - public var updateUserClientFromIsNewClient_MockError: Error? - public var updateUserClientFromIsNewClient_MockMethod: ((WireDataModel.UserClient, WireAPI.SelfUserClient, Bool) async throws -> Void)? + // MARK: - deletePushToken - public func updateUserClient(_ localClient: WireDataModel.UserClient, from remoteClient: WireAPI.SelfUserClient, isNewClient: Bool) async throws { - updateUserClientFromIsNewClient_Invocations.append((localClient: localClient, remoteClient: remoteClient, isNewClient: isNewClient)) + public var deletePushToken_Invocations: [Void] = [] + public var deletePushToken_MockMethod: (() -> Void)? - if let error = updateUserClientFromIsNewClient_MockError { - throw error - } + public func deletePushToken() { + deletePushToken_Invocations.append(()) - guard let mock = updateUserClientFromIsNewClient_MockMethod else { - fatalError("no mock for `updateUserClientFromIsNewClient`") + guard let mock = deletePushToken_MockMethod else { + fatalError("no mock for `deletePushToken`") } - try await mock(localClient, remoteClient, isNewClient) + mock() } // MARK: - addSelfLegalHoldRequest @@ -1664,32 +2310,55 @@ public class MockUserLocalStoreProtocol: UserLocalStoreProtocol { // MARK: - persistUser - public var persistUserFrom_Invocations: [WireAPI.User] = [] - public var persistUserFrom_MockMethod: ((WireAPI.User) async -> Void)? + public var persistUserUserInfo_Invocations: [NewUserInfo] = [] + public var persistUserUserInfo_MockMethod: ((NewUserInfo) async -> Void)? - public func persistUser(from user: WireAPI.User) async { - persistUserFrom_Invocations.append(user) + public func persistUser(userInfo: NewUserInfo) async { + persistUserUserInfo_Invocations.append(userInfo) - guard let mock = persistUserFrom_MockMethod else { - fatalError("no mock for `persistUserFrom`") + guard let mock = persistUserUserInfo_MockMethod else { + fatalError("no mock for `persistUserUserInfo`") } - await mock(user) + await mock(userInfo) } // MARK: - updateUser - public var updateUserFrom_Invocations: [UserUpdateEvent] = [] - public var updateUserFrom_MockMethod: ((UserUpdateEvent) async -> Void)? + public var updateUserUserUpdateInfo_Invocations: [UserUpdateInfo] = [] + public var updateUserUserUpdateInfo_MockMethod: ((UserUpdateInfo) async -> Void)? - public func updateUser(from event: UserUpdateEvent) async { - updateUserFrom_Invocations.append(event) + public func updateUser(userUpdateInfo: UserUpdateInfo) async { + updateUserUserUpdateInfo_Invocations.append(userUpdateInfo) - guard let mock = updateUserFrom_MockMethod else { - fatalError("no mock for `updateUserFrom`") + guard let mock = updateUserUserUpdateInfo_MockMethod else { + fatalError("no mock for `updateUserUserUpdateInfo`") } - await mock(event) + await mock(userUpdateInfo) + } + + // MARK: - fetchAllUserIDsWithOneOnOneConversation + + public var fetchAllUserIDsWithOneOnOneConversation_Invocations: [Void] = [] + public var fetchAllUserIDsWithOneOnOneConversation_MockError: Error? + public var fetchAllUserIDsWithOneOnOneConversation_MockMethod: (() async throws -> [WireDataModel.QualifiedID])? + public var fetchAllUserIDsWithOneOnOneConversation_MockValue: [WireDataModel.QualifiedID]? + + public func fetchAllUserIDsWithOneOnOneConversation() async throws -> [WireDataModel.QualifiedID] { + fetchAllUserIDsWithOneOnOneConversation_Invocations.append(()) + + if let error = fetchAllUserIDsWithOneOnOneConversation_MockError { + throw error + } + + if let mock = fetchAllUserIDsWithOneOnOneConversation_MockMethod { + return try await mock() + } else if let mock = fetchAllUserIDsWithOneOnOneConversation_MockValue { + return mock + } else { + fatalError("no mock for `fetchAllUserIDsWithOneOnOneConversation`") + } } } @@ -1701,6 +2370,26 @@ public class MockUserRepositoryProtocol: UserRepositoryProtocol { public init() {} + // MARK: - pullSelfUser + + public var pullSelfUser_Invocations: [Void] = [] + public var pullSelfUser_MockError: Error? + public var pullSelfUser_MockMethod: (() async throws -> Void)? + + public func pullSelfUser() async throws { + pullSelfUser_Invocations.append(()) + + if let error = pullSelfUser_MockError { + throw error + } + + guard let mock = pullSelfUser_MockMethod else { + fatalError("no mock for `pullSelfUser`") + } + + try await mock() + } + // MARK: - fetchSelfUser public var fetchSelfUser_Invocations: [Void] = [] @@ -1958,6 +2647,29 @@ public class MockUserRepositoryProtocol: UserRepositoryProtocol { } } + // MARK: - fetchAllUserIDsWithOneOnOneConversation + + public var fetchAllUserIDsWithOneOnOneConversation_Invocations: [Void] = [] + public var fetchAllUserIDsWithOneOnOneConversation_MockError: Error? + public var fetchAllUserIDsWithOneOnOneConversation_MockMethod: (() async throws -> [WireDataModel.QualifiedID])? + public var fetchAllUserIDsWithOneOnOneConversation_MockValue: [WireDataModel.QualifiedID]? + + public func fetchAllUserIDsWithOneOnOneConversation() async throws -> [WireDataModel.QualifiedID] { + fetchAllUserIDsWithOneOnOneConversation_Invocations.append(()) + + if let error = fetchAllUserIDsWithOneOnOneConversation_MockError { + throw error + } + + if let mock = fetchAllUserIDsWithOneOnOneConversation_MockMethod { + return try await mock() + } else if let mock = fetchAllUserIDsWithOneOnOneConversation_MockValue { + return mock + } else { + fatalError("no mock for `fetchAllUserIDsWithOneOnOneConversation`") + } + } + } // swiftlint:enable variable_name diff --git a/WireDomain/.swiftpm/WireDomainPackage.xctestplan b/WireDomain/Tests/TestPlans/AllTests.xctestplan similarity index 64% rename from WireDomain/.swiftpm/WireDomainPackage.xctestplan rename to WireDomain/Tests/TestPlans/AllTests.xctestplan index 455d5b6ee2f..444f2ef0e81 100644 --- a/WireDomain/.swiftpm/WireDomainPackage.xctestplan +++ b/WireDomain/Tests/TestPlans/AllTests.xctestplan @@ -1,14 +1,19 @@ { "configurations" : [ { - "id" : "9847EFE7-0EA9-4F10-ADDE-417328B9D738", - "name" : "Test Scheme Action", + "id" : "F5BC5855-65E0-4C6D-BEC0-97FFF7D13461", + "name" : "Configuration 1", "options" : { } } ], "defaultOptions" : { + "commandLineArgumentEntries" : [ + { + "argument" : "-com.apple.CoreData.ConcurrencyDebug 1" + } + ], "language" : "en", "region" : "DE", "testExecutionOrdering" : "random" diff --git a/WireDomain/Tests/WireDomainTests/Event Decryption/ProteusMessageDecryptorTests.swift b/WireDomain/Tests/WireDomainTests/Event Decryption/ProteusMessageDecryptorTests.swift index 53bf889c81a..9b23c1e3ea4 100644 --- a/WireDomain/Tests/WireDomainTests/Event Decryption/ProteusMessageDecryptorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Decryption/ProteusMessageDecryptorTests.swift @@ -132,41 +132,6 @@ final class ProteusMessageDecryptorTests: XCTestCase { } } - func testItThrowsWhenCiphertextIsTooBig() async throws { - // Given a message that exceeds the max ciphertext size - let longMessage = String(repeating: "!", count: 20_000) - let invalidEvent = Scaffolding.makeEvent(content: .ciphertext(longMessage)) - - // When - do { - _ = try await sut.decryptedEventData(from: invalidEvent) - XCTFail("expected an error but none was thrown") - return - } catch ProteusError.decodeError { - // Then we got the right error - } catch { - XCTFail("unexpected error: \(error)") - } - } - - func testItThrowsWhenExternalCiphertextIsTooBig() async throws { - // Given an external message that exceeds the max ciphertext size - var invalidEvent = Scaffolding.makeEvent(content: .ciphertext("valid message")) - let longMessage = String(repeating: "!", count: 20_000) - invalidEvent.externalData = .ciphertext(longMessage) - - // When - do { - _ = try await sut.decryptedEventData(from: invalidEvent) - XCTFail("expected an error but none was thrown") - return - } catch ProteusError.decodeError { - // Then we got the right error - } catch { - XCTFail("unexpected error: \(error)") - } - } - func testItDecryptsAnEventFromANewSenderAndUpdatesSecurityLevel() async throws { // Given try await context.perform { [context] in diff --git a/WireDomain/Tests/WireDomainTests/Event Decryption/UpdateEventDecryptorTests.swift b/WireDomain/Tests/WireDomainTests/Event Decryption/UpdateEventDecryptorTests.swift index 49d2b082dbf..36985156a54 100644 --- a/WireDomain/Tests/WireDomainTests/Event Decryption/UpdateEventDecryptorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Decryption/UpdateEventDecryptorTests.swift @@ -134,7 +134,7 @@ final class UpdateEventDecryptorTests: XCTestCase { // Mock proteusMessageDecryptor.decryptedEventDataFrom_MockMethod = { _ in - throw ProteusError.invalidSignature + throw ProteusService.DecryptionError.failedToDecryptData(.Other(1)) } // When @@ -169,7 +169,7 @@ final class UpdateEventDecryptorTests: XCTestCase { let lastMessage = try XCTUnwrap(conversation.lastMessage as? ZMSystemMessage) XCTAssertEqual(lastMessage.systemMessageType, .decryptionFailed) - XCTAssertEqual(lastMessage.decryptionErrorCode?.intValue, ProteusError.invalidSignature.rawValue) + XCTAssertEqual(lastMessage.decryptionErrorCode?.intValue, 1) XCTAssertEqual(lastMessage.serverTimestamp, Scaffolding.timestamp) XCTAssertEqual(lastMessage.sender, alice) XCTAssertEqual(lastMessage.clients, [aliceClient]) @@ -189,43 +189,7 @@ final class UpdateEventDecryptorTests: XCTestCase { // Mock proteusMessageDecryptor.decryptedEventDataFrom_MockMethod = { _ in - throw ProteusError.duplicateMessage - } - - // When - let events = try await sut.decryptEvents(in: envelope) - - // Then we skipped over the proteus message. - XCTAssertEqual(events, [.user(.pushRemove)]) - - // Then no system message was appended. - try await context.perform { [context] in - let conversation = try XCTUnwrap( - ZMConversation.fetch( - with: Scaffolding.conversationID.uuid, - domain: Scaffolding.conversationID.domain, - in: context - ) - ) - - XCTAssertNil(conversation.lastMessage) - } - } - - func testWhenOutdatedMessageErrorIsThrownThenNoSystemMessageIsAppended() async throws { - // Given some events. - let envelope = UpdateEventEnvelope( - id: UUID(), - events: [ - .conversation(.proteusMessageAdd(Scaffolding.proteusMessage)), - .user(.pushRemove) - ], - isTransient: false - ) - - // Mock - proteusMessageDecryptor.decryptedEventDataFrom_MockMethod = { _ in - throw ProteusError.outdatedMessage + throw ProteusService.DecryptionError.failedToDecryptData(.DuplicateMessage) } // When diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationMLSWelcomeEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationMLSWelcomeEventProcessorTests.swift new file mode 100644 index 00000000000..9bfc419f09e --- /dev/null +++ b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationMLSWelcomeEventProcessorTests.swift @@ -0,0 +1,131 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel +import WireDataModelSupport +import WireDomainSupport +import XCTest +@testable import WireAPI +@testable import WireDomain + +final class ConversationMLSWelcomeEventProcessorTests: XCTestCase { + + private var sut: ConversationMLSWelcomeEventProcessor! + private var modelHelper: ModelHelper! + private var coreDataStack: CoreDataStack! + private var coreDataStackHelper: CoreDataStackHelper! + private var conversationRepository: MockConversationRepositoryProtocol! + private var conversationLocalStore: MockConversationLocalStoreProtocol! + private var userLocalStore: MockUserLocalStoreProtocol! + private var mlsService: MockMLSServiceInterface! + private var mlsDecryptionService: MockMLSDecryptionServiceInterface! + private var oneOnOneResolver: MockOneOnOneResolverProtocol! + + private var context: NSManagedObjectContext { + coreDataStack.syncContext + } + + override func setUp() async throws { + modelHelper = ModelHelper() + coreDataStackHelper = CoreDataStackHelper() + coreDataStack = try await coreDataStackHelper.createStack() + conversationRepository = MockConversationRepositoryProtocol() + conversationLocalStore = MockConversationLocalStoreProtocol() + userLocalStore = MockUserLocalStoreProtocol() + mlsService = MockMLSServiceInterface() + mlsDecryptionService = MockMLSDecryptionServiceInterface() + oneOnOneResolver = MockOneOnOneResolverProtocol() + + sut = ConversationMLSWelcomeEventProcessor( + conversationRepository: conversationRepository, + conversationLocalStore: conversationLocalStore, + mlsService: mlsService, + mlsDecryptionService: mlsDecryptionService, + oneOnOneResolver: oneOnOneResolver + ) + } + + override func tearDown() async throws { + modelHelper = nil + coreDataStack = nil + sut = nil + try coreDataStackHelper.cleanupDirectory() + coreDataStackHelper = nil + conversationRepository = nil + conversationLocalStore = nil + userLocalStore = nil + mlsService = nil + mlsDecryptionService = nil + oneOnOneResolver = nil + } + + // MARK: - Tests + + func testProcessEvent_It_Invokes_Repository_Local_Store_And_MLS_Services_Methods() async throws { + + // Mock + + let conversation = await context.perform { [self] in + modelHelper.createGroupConversation( + id: Scaffolding.conversationID.uuid, + domain: Scaffolding.conversationID.domain, + in: context + ) + } + + mlsDecryptionService.processWelcomeMessageWelcomeMessage_MockValue = Scaffolding.mlsGroupID + conversationRepository.fetchConversationIdDomain_MockValue = conversation + conversationLocalStore.storeMLSConversationEstablishedMlsGroupIDConversation_MockMethod = { _, _ in } + conversationLocalStore.updateOrCreateMLSGroupGroupID_MockMethod = { _ in } + mlsService.uploadKeyPackagesIfNeeded_MockMethod = {} + conversationLocalStore.fetchOtherUserIDInOneOnOneConversationConversation_MockValue = Scaffolding.qualifiedID + oneOnOneResolver.resolveOneOnOneConversationWith_MockMethod = { _ in } + + // When + + try await sut.processEvent(Scaffolding.event) + + // Then + + XCTAssertEqual(mlsDecryptionService.processWelcomeMessageWelcomeMessage_Invocations.count, 1) + XCTAssertEqual(conversationRepository.fetchConversationIdDomain_Invocations.count, 1) + XCTAssertEqual( + conversationLocalStore.storeMLSConversationEstablishedMlsGroupIDConversation_Invocations.count, + 1 + ) + XCTAssertEqual(conversationLocalStore.updateOrCreateMLSGroupGroupID_Invocations.count, 1) + XCTAssertEqual(mlsService.uploadKeyPackagesIfNeeded_Invocations.count, 1) + XCTAssertEqual(conversationLocalStore.fetchOtherUserIDInOneOnOneConversationConversation_Invocations.count, 1) + XCTAssertEqual(oneOnOneResolver.resolveOneOnOneConversationWith_Invocations.count, 1) + } + + private enum Scaffolding { + static let domain = "domain.com" + static let conversationID = ConversationID(uuid: .mockID1, domain: domain) + static let senderID = UserID(uuid: .mockID2, domain: domain) + static let mlsGroupID = MLSGroupID.random() + static let qualifiedID = WireDataModel.QualifiedID(uuid: .mockID1, domain: domain) + + static let event = ConversationMLSWelcomeEvent( + conversationID: conversationID, + senderID: senderID, + welcomeMessage: Data.random().base64EncodedString() + ) + } + +} diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationMessageTimerUpdateEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationMessageTimerUpdateEventProcessorTests.swift new file mode 100644 index 00000000000..1b96c363d68 --- /dev/null +++ b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationMessageTimerUpdateEventProcessorTests.swift @@ -0,0 +1,109 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel +import WireDataModelSupport +import WireDomainSupport +import XCTest +@testable import WireAPI +@testable import WireDomain + +final class ConversationMessageTimerUpdateEventProcessorTests: XCTestCase { + + private var sut: ConversationMessageTimerUpdateEventProcessor! + private var conversationLocalStore: MockConversationLocalStoreProtocol! + private var messageLocalStore: MockMessageLocalStoreProtocol! + private var coreDataStack: CoreDataStack! + private var coreDataStackHelper: CoreDataStackHelper! + private var modelHelper: ModelHelper! + + private var context: NSManagedObjectContext { + coreDataStack.syncContext + } + + override func setUp() async throws { + try await super.setUp() + + modelHelper = ModelHelper() + coreDataStackHelper = CoreDataStackHelper() + coreDataStack = try await coreDataStackHelper.createStack() + + conversationLocalStore = MockConversationLocalStoreProtocol() + messageLocalStore = MockMessageLocalStoreProtocol() + + sut = ConversationMessageTimerUpdateEventProcessor( + conversationLocalStore: conversationLocalStore, + messageLocalStore: messageLocalStore + ) + } + + override func tearDown() async throws { + try await super.tearDown() + sut = nil + conversationLocalStore = nil + messageLocalStore = nil + modelHelper = nil + coreDataStack = nil + try coreDataStackHelper.cleanupDirectory() + coreDataStackHelper = nil + } + + // MARK: - Tests + + func testProcessEvent_It_Invokes_Repo_Methods() async { + // Mock + + let conversation = await context.perform { [self] in + return modelHelper.createGroupConversation(in: context) + } + + conversationLocalStore.fetchOrCreateConversationIdDomain_MockValue = conversation + conversationLocalStore.conversationMessageDestructionTimeout_MockValue = .fiveMinutes + conversationLocalStore.storeConversationTimeoutValueFor_MockMethod = { _, _ in } + messageLocalStore + .addSystemMessageToConversationMessageTypeConversationIDConversationDomain_MockMethod = { _, _, _ in } + + // When + + await sut.processEvent(Scaffolding.event) + + // Then + + XCTAssertEqual(conversationLocalStore.fetchOrCreateConversationIdDomain_Invocations.count, 1) + XCTAssertEqual(conversationLocalStore.conversationMessageDestructionTimeout_Invocations.count, 1) + XCTAssertEqual(conversationLocalStore.storeConversationTimeoutValueFor_Invocations.count, 1) + XCTAssertEqual( + messageLocalStore.addSystemMessageToConversationMessageTypeConversationIDConversationDomain_Invocations + .count, + 1 + ) + } + + private enum Scaffolding { + static let id = UUID() + static let domain = "domain.com" + static let event = ConversationMessageTimerUpdateEvent( + conversationID: ConversationID(uuid: id, domain: domain), + senderID: UserID(uuid: id, domain: domain), + timestamp: .now, + newTimer: 10_000 + ) + + } + +} diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationReceiptModeUpdateEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationReceiptModeUpdateEventProcessorTests.swift index 4da9e925ed6..864d47c9020 100644 --- a/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationReceiptModeUpdateEventProcessorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationReceiptModeUpdateEventProcessorTests.swift @@ -29,6 +29,7 @@ final class ConversationReceiptModeUpdateEventProcessorTests: XCTestCase { private var userRepository: MockUserRepositoryProtocol! private var conversationRepository: MockConversationRepositoryProtocol! private var conversationLocalStore: MockConversationLocalStoreProtocol! + private var messageRepository: MockMessageRepositoryProtocol! private var coreDataStack: CoreDataStack! private var coreDataStackHelper: CoreDataStackHelper! private var modelHelper: ModelHelper! @@ -38,22 +39,23 @@ final class ConversationReceiptModeUpdateEventProcessorTests: XCTestCase { } override func setUp() async throws { - try await super.setUp() modelHelper = ModelHelper() coreDataStackHelper = CoreDataStackHelper() coreDataStack = try await coreDataStackHelper.createStack() userRepository = MockUserRepositoryProtocol() conversationRepository = MockConversationRepositoryProtocol() conversationLocalStore = MockConversationLocalStoreProtocol() + messageRepository = MockMessageRepositoryProtocol() + sut = ConversationReceiptModeUpdateEventProcessor( userRepository: userRepository, conversationRepository: conversationRepository, - conversationLocalStore: conversationLocalStore + conversationLocalStore: conversationLocalStore, + messageRepository: messageRepository ) } override func tearDown() async throws { - try await super.tearDown() modelHelper = nil coreDataStack = nil userRepository = nil @@ -62,6 +64,7 @@ final class ConversationReceiptModeUpdateEventProcessorTests: XCTestCase { sut = nil try coreDataStackHelper.cleanupDirectory() coreDataStackHelper = nil + messageRepository = nil } // MARK: - Tests @@ -78,11 +81,9 @@ final class ConversationReceiptModeUpdateEventProcessorTests: XCTestCase { userRepository.fetchUserIdDomain_MockValue = user conversationRepository.fetchConversationIdDomain_MockValue = conversation - conversationRepository.addSystemMessageTo_MockMethod = { _, _ in } + messageRepository + .addMessageToConversationMessageTypeConversationIDConversationDomain_MockMethod = { _, _, _ in } conversationLocalStore.storeConversationHasReadReceiptsEnabledFor_MockMethod = { _, _ in } - conversationLocalStore.isConversationArchived_MockValue = true - conversationLocalStore.conversationMutedMessageTypes_MockValue = MutedMessageTypes.none - conversationLocalStore.storeConversationIsArchivedFor_MockMethod = { _, _ in } // When @@ -92,11 +93,11 @@ final class ConversationReceiptModeUpdateEventProcessorTests: XCTestCase { XCTAssertEqual(userRepository.fetchUserIdDomain_Invocations.count, 1) XCTAssertEqual(conversationRepository.fetchConversationIdDomain_Invocations.count, 1) - XCTAssertEqual(conversationRepository.addSystemMessageTo_Invocations.count, 1) + XCTAssertEqual( + messageRepository.addMessageToConversationMessageTypeConversationIDConversationDomain_Invocations.count, + 1 + ) XCTAssertEqual(conversationLocalStore.storeConversationHasReadReceiptsEnabledFor_Invocations.count, 1) - XCTAssertEqual(conversationLocalStore.isConversationArchived_Invocations.count, 1) - XCTAssertEqual(conversationLocalStore.conversationMutedMessageTypes_Invocations.count, 1) - XCTAssertEqual(conversationLocalStore.storeConversationIsArchivedFor_Invocations.count, 1) } private enum Scaffolding { @@ -105,7 +106,7 @@ final class ConversationReceiptModeUpdateEventProcessorTests: XCTestCase { static let event = ConversationReceiptModeUpdateEvent( conversationID: ConversationID(uuid: id, domain: domain), senderID: UserID(uuid: id, domain: domain), - newRecieptMode: 1 + newReceiptMode: 1 ) } } diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationRenameEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationRenameEventProcessorTests.swift new file mode 100644 index 00000000000..42443cc0cc7 --- /dev/null +++ b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationRenameEventProcessorTests.swift @@ -0,0 +1,76 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDomainSupport +import XCTest +@testable import WireAPI +@testable import WireDomain + +final class ConversationRenameEventProcessorTests: XCTestCase { + + private var sut: ConversationRenameEventProcessor! + private var conversationRepository: MockConversationRepositoryProtocol! + + override func setUp() async throws { + conversationRepository = MockConversationRepositoryProtocol() + sut = ConversationRenameEventProcessor( + repository: conversationRepository + ) + } + + override func tearDown() async throws { + conversationRepository = nil + sut = nil + } + + // MARK: - Tests + + func testProcessEvent_It_Invokes_Repo_And_Local_Store_Methods() async throws { + + // Mock + + conversationRepository + .updateConversationNameNewNameConversationIDConversationDomainSenderIDSenderDomainDate_MockMethod = + { _, _, _, _, _, _ in } + + // When + + try await sut.processEvent(Scaffolding.event) + + // Then + + XCTAssertEqual( + conversationRepository + .updateConversationNameNewNameConversationIDConversationDomainSenderIDSenderDomainDate_Invocations + .count, + 1 + ) + } + + private enum Scaffolding { + static let id = UUID.mockID1 + static let domain = "domain.com" + + static let event = ConversationRenameEvent( + conversationID: ConversationID(uuid: id, domain: domain), + senderID: UserID(uuid: id, domain: domain), + timestamp: .now, + newName: "New conversation name" + ) + } +} diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessorTests.swift new file mode 100644 index 00000000000..5f95e8dc42b --- /dev/null +++ b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationTypingEventProcessorTests.swift @@ -0,0 +1,105 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel +import WireDataModelSupport +import WireDomainSupport +import XCTest +@testable import WireAPI +@testable import WireDomain + +final class ConversationTypingEventProcessorTests: XCTestCase { + + private var sut: ConversationTypingEventProcessor! + private var userRepository: MockUserRepositoryProtocol! + private var conversationRepository: MockConversationRepositoryProtocol! + private var conversationLocalStore: MockConversationLocalStoreProtocol! + private var coreDataStack: CoreDataStack! + private var coreDataStackHelper: CoreDataStackHelper! + private var modelHelper: ModelHelper! + + private var context: NSManagedObjectContext { + coreDataStack.syncContext + } + + override func setUp() async throws { + modelHelper = ModelHelper() + coreDataStackHelper = CoreDataStackHelper() + coreDataStack = try await coreDataStackHelper.createStack() + userRepository = MockUserRepositoryProtocol() + conversationRepository = MockConversationRepositoryProtocol() + conversationLocalStore = MockConversationLocalStoreProtocol() + + sut = ConversationTypingEventProcessor( + conversationRepository: conversationRepository, + conversationLocalStore: conversationLocalStore, + userRepository: userRepository + ) + } + + override func tearDown() async throws { + modelHelper = nil + coreDataStack = nil + userRepository = nil + conversationRepository = nil + conversationLocalStore = nil + sut = nil + try coreDataStackHelper.cleanupDirectory() + coreDataStackHelper = nil + } + + // MARK: - Tests + + func testProcessEvent_It_Invokes_Repo_And_Local_Store_Methods() async throws { + + // Mock + + let (user, conversation) = await context.perform { [self] in + let user = modelHelper.createUser(in: context) + let conversation = modelHelper.createGroupConversation(in: context) + + return (user, conversation) + } + + userRepository.fetchOrCreateUserIdDomain_MockValue = user + conversationRepository.fetchOrCreateConversationIdDomain_MockValue = conversation + conversationRepository.updateTypingUsers_MockMethod = { _ in } + conversationLocalStore.obtainPermanentIDsUserConversation_MockMethod = { _, _ in } + + // When + + await sut.processEvent(Scaffolding.event) + + // Then + + XCTAssertEqual(conversationLocalStore.obtainPermanentIDsUserConversation_Invocations.count, 1) + XCTAssertEqual(userRepository.fetchOrCreateUserIdDomain_Invocations.count, 1) + XCTAssertEqual(conversationRepository.fetchOrCreateConversationIdDomain_Invocations.count, 1) + XCTAssertEqual(conversationRepository.updateTypingUsers_Invocations.count, 1) + } + + private enum Scaffolding { + static let id = UUID() + static let domain = "domain.com" + static let event = ConversationTypingEvent( + conversationID: ConversationID(uuid: id, domain: domain), + senderID: UserID(uuid: id, domain: domain), + isTyping: true + ) + } +} diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationTypingUsersTimeoutTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationTypingUsersTimeoutTests.swift new file mode 100644 index 00000000000..aa5f7d964d5 --- /dev/null +++ b/WireDomain/Tests/WireDomainTests/Event Processing/ConversationEventProcessor/ConversationTypingUsersTimeoutTests.swift @@ -0,0 +1,297 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel +import WireDataModelSupport +import XCTest +@testable import WireDomain + +final class ConversationTypingUsersTimeoutTests: XCTestCase { + + private var sut: ConversationTypingUsersTimeout! + private var coreDataStack: CoreDataStack! + private var coreDataStackHelper: CoreDataStackHelper! + private var modelHelper: ModelHelper! + + private var context: NSManagedObjectContext { + coreDataStack.syncContext + } + + private var conversationAObjectID: NSManagedObjectID! + private var conversationBObjectID: NSManagedObjectID! + private var userAObjectID: NSManagedObjectID! + private var userBObjectID: NSManagedObjectID! + + override func setUp() async throws { + modelHelper = ModelHelper() + coreDataStackHelper = CoreDataStackHelper() + coreDataStack = try await coreDataStackHelper.createStack() + + sut = ConversationTypingUsersTimeout() + + await setupObjectIDs() + } + + override func tearDown() async throws { + sut = nil + modelHelper = nil + coreDataStack = nil + try coreDataStackHelper.cleanupDirectory() + coreDataStackHelper = nil + conversationAObjectID = nil + conversationBObjectID = nil + userAObjectID = nil + userBObjectID = nil + } + + // MARK: - Adding / Removing Users + + func testContains_It_Does_Not_Contain_Users_That_We_Have_Not_Added() { + + // Given, then + + XCTAssertFalse(sut.contains(userAObjectID, for: conversationAObjectID)) + XCTAssertFalse(sut.contains(userBObjectID, for: conversationBObjectID)) + } + + func testContains_It_Can_Add_A_User() { + + // When + + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: Date()) + + // Then + + XCTAssertTrue(sut.contains(userAObjectID, for: conversationAObjectID)) + } + + func testRemove_It_Can_Remove_A_User() { + + // Given + + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: Date()) + sut.add(userBObjectID, for: conversationAObjectID, withTimeout: Date()) + + // When + + sut.remove(userAObjectID, for: conversationAObjectID) + + // Then + + XCTAssertFalse(sut.contains(userAObjectID, for: conversationAObjectID)) + XCTAssertTrue(sut.contains(userBObjectID, for: conversationAObjectID)) + } + + func test_That_First_Timeout_Is_Nil_If_Timeouts_Is_Empty() { + + // Given, Then + + XCTAssertNil(sut.firstTimeout) + } + + func testRemove_First_Timeout_Is_Nil_For_Users_Added_And_Removed_Again() { + + // Given + + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: Date()) + + // When + + sut.remove(userAObjectID, for: conversationAObjectID) + + // Then + + XCTAssertNil(sut.firstTimeout) + } + + func testThatItReturnsTheTimeoutWhenAUserIsAdded() { + // Given + let timeout = Date() + + // When + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: timeout) + + // Then + XCTAssertEqual(sut.firstTimeout, timeout) + } + + func testAdd_It_Returns_The_Earliest_Timeout_When_Multiple_Are_Added() { + + // Given + + let timeout1 = Date() + let timeout2 = timeout1.addingTimeInterval(10) + let timeout3 = timeout2.addingTimeInterval(20) + + // When + + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: timeout1) + sut.add(userAObjectID, for: conversationBObjectID, withTimeout: timeout2) + sut.add(userBObjectID, for: conversationAObjectID, withTimeout: timeout3) + + // Then + + XCTAssertEqual(sut.firstTimeout, timeout1) + } + + func testAdd_It_Returns_The_Last_Set_Timeout_When_Added_Multiple_Times_For_The_Same_User_And_Conversation() { + + // Given + + let timeout1 = Date() + let timeout2 = timeout1.addingTimeInterval(10) + let timeout3 = timeout2.addingTimeInterval(20) + + // When + + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: timeout1) + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: timeout2) + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: timeout3) + + // Then + + XCTAssertEqual(sut.firstTimeout, timeout3) + } + + func testAdd_It_Returns_The_Currently_Typing_User_Ids() { + + // Given + + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: Date()) + sut.add(userBObjectID, for: conversationAObjectID, withTimeout: Date()) + + // When + + let result = sut.userIds(in: conversationAObjectID) + + // Then + + XCTAssertEqual(result, Set([userAObjectID, userBObjectID])) + } + + func testUserIds_It_Returns_An_Empty_Set_When_No_Users_Are_Typing() { + + // When + + let result = sut.userIds(in: conversationAObjectID) + + // Then + + XCTAssertEqual(result, Set()) + } + + func testPruneConversations_It_Returns_An_Empty_Set_When_Pruning_And_Nothing_Was_Added() { + // When + let result = sut.pruneConversationsThatHaveTimoutBefore(date: Date(timeIntervalSinceNow: -10)) + + // Then + XCTAssertEqual(result, Set()) + } + + func testPruneConversations_It_Returns_An_Empty_Set_When_Pruning_And_Nothing_Has_Expired() { + + // Given + + let timeout = Date(timeIntervalSinceNow: 10) + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: timeout) + + // When + + let result = sut.pruneConversationsThatHaveTimoutBefore(date: Date()) + + // Then + + XCTAssertEqual(result, Set()) + } + + func testPruneConversationsIt_Returns_A_Pruned_Conversation() { + + // Given + + let timeout1 = Date(timeIntervalSinceNow: 10) + let timeout2 = Date(timeIntervalSinceNow: 20) + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: timeout1) + + // When + + let result = sut.pruneConversationsThatHaveTimoutBefore(date: timeout2) + + // Then + + XCTAssertEqual(result, Set([conversationAObjectID])) + } + + func testPruneConversations_It_Returns_Multiple_Pruned_Conversations() { + + // Given + + let timeout1 = Date(timeIntervalSinceNow: 10) + let timeout2 = Date(timeIntervalSinceNow: 20) + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: timeout1) + sut.add(userBObjectID, for: conversationBObjectID, withTimeout: timeout1) + + // When + + let result = sut.pruneConversationsThatHaveTimoutBefore(date: timeout2) + + // Then + + XCTAssertEqual(result, Set([conversationAObjectID, conversationBObjectID])) + } + + func testPruneConversations_It_Removes_Users_When_Pruning() { + + // Given + + let timeout1 = Date(timeIntervalSinceNow: 10) + let timeout2 = Date(timeIntervalSinceNow: 15) + let timeout3 = Date(timeIntervalSinceNow: 20) + sut.add(userAObjectID, for: conversationAObjectID, withTimeout: timeout1) + sut.add(userBObjectID, for: conversationAObjectID, withTimeout: timeout3) + + // When + + _ = sut.pruneConversationsThatHaveTimoutBefore(date: timeout2) + + // Then + + XCTAssertFalse(sut.contains(userAObjectID, for: conversationAObjectID)) + XCTAssertTrue(sut.contains(userBObjectID, for: conversationAObjectID)) + XCTAssertEqual(sut.userIds(in: conversationAObjectID), Set([userBObjectID])) + } + + private func setupObjectIDs() async { + await context.perform { [self] in + conversationAObjectID = modelHelper.createGroupConversation( + in: context + ).objectID + + conversationBObjectID = modelHelper.createGroupConversation( + in: context + ).objectID + + userAObjectID = modelHelper.createUser( + in: context + ).objectID + + userBObjectID = modelHelper.createUser( + in: context + ).objectID + } + } +} diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/FeatureConfigEventProcessor/FeatureConfigUpdateEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/FeatureConfigEventProcessor/FeatureConfigUpdateEventProcessorTests.swift index 4bb115e0ed8..b3e7c2e0312 100644 --- a/WireDomain/Tests/WireDomainTests/Event Processing/FeatureConfigEventProcessor/FeatureConfigUpdateEventProcessorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Processing/FeatureConfigEventProcessor/FeatureConfigUpdateEventProcessorTests.swift @@ -19,6 +19,7 @@ import WireAPI import WireDomainSupport import XCTest + @testable import WireDomain final class FeatureConfigUpdateEventProcessorTests: XCTestCase { diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamMemberLeaveEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamMemberLeaveEventProcessorTests.swift index 8cdd158eaa5..63ec1f7d2c8 100644 --- a/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamMemberLeaveEventProcessorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamMemberLeaveEventProcessorTests.swift @@ -45,7 +45,7 @@ final class TeamMemberLeaveEventProcessorTests: XCTestCase { func testProcessEvent_It_Invokes_Delete_Team_Membership_Repo_Method() async throws { // Mock - teamRepository.deleteMembershipForDomainAt_MockMethod = { _, _, _ in } + teamRepository.deleteMembershipUserIDDomainDate_MockMethod = { _, _, _ in } // When @@ -53,7 +53,7 @@ final class TeamMemberLeaveEventProcessorTests: XCTestCase { // Then - XCTAssertEqual(teamRepository.deleteMembershipForDomainAt_Invocations.count, 1) + XCTAssertEqual(teamRepository.deleteMembershipUserIDDomainDate_Invocations.count, 1) } private enum Scaffolding { diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserClientAddEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserClientAddEventProcessorTests.swift index a82eb09788d..51d4a6cf656 100644 --- a/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserClientAddEventProcessorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserClientAddEventProcessorTests.swift @@ -66,11 +66,11 @@ final class UserClientAddEventProcessorTests: XCTestCase { modelHelper.createSelfClient(in: context) } - userClientsRepository.fetchOrCreateClientWith_MockMethod = { _ in + userClientsRepository.fetchOrCreateClientId_MockMethod = { _ in (userClient, true) } - userClientsRepository.updateClientWithFromIsNewClient_MockMethod = { _, _, _ in } + userClientsRepository.updateClientIdFromIsNewClient_MockMethod = { _, _, _ in } // When @@ -78,8 +78,8 @@ final class UserClientAddEventProcessorTests: XCTestCase { // Then - XCTAssertEqual(userClientsRepository.fetchOrCreateClientWith_Invocations.count, 1) - XCTAssertEqual(userClientsRepository.updateClientWithFromIsNewClient_Invocations.count, 1) + XCTAssertEqual(userClientsRepository.fetchOrCreateClientId_Invocations.count, 1) + XCTAssertEqual(userClientsRepository.updateClientIdFromIsNewClient_Invocations.count, 1) } private enum Scaffolding { diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserConnectionEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserConnectionEventProcessorTests.swift index 9f63b2bbfc4..0dbe51b5392 100644 --- a/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserConnectionEventProcessorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserConnectionEventProcessorTests.swift @@ -57,7 +57,7 @@ final class UserConnectionEventProcessorTests: XCTestCase { // Mock connectionsRepository.updateConnection_MockMethod = { _ in } - oneOnOneResolver.invoke_MockMethod = {} + oneOnOneResolver.resolveAllOneOnOneConversations_MockMethod = {} // When @@ -66,7 +66,7 @@ final class UserConnectionEventProcessorTests: XCTestCase { // Then XCTAssertEqual(connectionsRepository.updateConnection_Invocations, [event.connection]) - XCTAssertEqual(oneOnOneResolver.invoke_Invocations.count, 1) + XCTAssertEqual(oneOnOneResolver.resolveAllOneOnOneConversations_Invocations.count, 1) } private enum Scaffolding { diff --git a/WireDomain/Tests/WireDomainTests/LocalStores/ConnectionsLocalStoreTests.swift b/WireDomain/Tests/WireDomainTests/LocalStores/ConnectionsLocalStoreTests.swift new file mode 100644 index 00000000000..c53178835bd --- /dev/null +++ b/WireDomain/Tests/WireDomainTests/LocalStores/ConnectionsLocalStoreTests.swift @@ -0,0 +1,175 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel +import WireDataModelSupport +import XCTest +@testable import WireDomain + +final class ConnectionsLocalStoreTests: XCTestCase { + + private var sut: ConnectionsLocalStore! + private var stack: CoreDataStack! + private var coreDataStackHelper: CoreDataStackHelper! + private var modelHelper: ModelHelper! + + private var context: NSManagedObjectContext { + stack.syncContext + } + + override func setUp() async throws { + BackendInfo.isFederationEnabled = false + + modelHelper = ModelHelper() + coreDataStackHelper = CoreDataStackHelper() + stack = try await coreDataStackHelper.createStack() + sut = ConnectionsLocalStore(context: context) + } + + override func tearDown() async throws { + stack = nil + sut = nil + try coreDataStackHelper.cleanupDirectory() + modelHelper = nil + coreDataStackHelper = nil + } + + // MARK: - Tests + + func testPullConnections_GivenConnectionDoesNotExist_FederationDisabled() async throws { + try await internalTestPullConnections_GivenConnectionDoesNotExist( + federationEnabled: false + ) + } + + func testPullConnections_GivenConnectionDoesNotExist_FederationEnabled() async throws { + BackendInfo.isFederationEnabled = true + try await internalTestPullConnections_GivenConnectionDoesNotExist( + federationEnabled: true + ) + } + + // MARK: Private + + func internalTestPullConnections_GivenConnectionDoesNotExist( + federationEnabled: Bool, + file: StaticString = #file, + line: UInt = #line + ) async throws { + // Mock + + let connection = Scaffolding.connection + + // When + + try await sut.storeConnection(connection) + + // Then + + try await context.perform { [context] in + // There is a connection in the database. + let storedConnection = try XCTUnwrap(ZMConnection.fetch( + userID: Scaffolding.member2ID.uuid, + domain: Scaffolding.member2ID.domain, + in: context + )) + + XCTAssertEqual(storedConnection.lastUpdateDateInGMT, connection.lastUpdate) + + XCTAssertEqual(storedConnection.to.remoteIdentifier, connection.receiverID) + if federationEnabled { + XCTAssertEqual(storedConnection.to.domain, connection.receiverQualifiedID?.domain) + } else { + XCTAssertNil(storedConnection.to.domain) + } + XCTAssertEqual(storedConnection.status, ZMConnectionStatus.accepted) + + let relatedConversation = try XCTUnwrap(storedConnection.to.oneOnOneConversation) + XCTAssertEqual(relatedConversation.remoteIdentifier, connection.qualifiedConversationID?.uuid) + + if federationEnabled { + XCTAssertEqual(relatedConversation.domain, connection.qualifiedConversationID?.domain) + } else { + XCTAssertNil(relatedConversation.domain) + } + + XCTAssertTrue(relatedConversation.needsToBeUpdatedFromBackend) + } + } + + func testUpdateConnection_It_Successfully_Updates_Connection_Locally() async throws { + // Given + + let connection = Scaffolding.connection + + // When + + try await sut.storeConnection(connection) + + // Then + + try await context.perform { [context] in + let storedConnection = try XCTUnwrap(ZMConnection.fetch( + userID: Scaffolding.member2ID.uuid, + domain: Scaffolding.member2ID.domain, + in: context + )) + + XCTAssertEqual(storedConnection.lastUpdateDateInGMT, connection.lastUpdate) + + XCTAssertEqual(storedConnection.to.remoteIdentifier, connection.receiverID) + XCTAssertNil(storedConnection.to.domain) + XCTAssertEqual(storedConnection.status, ZMConnectionStatus.accepted) + + let relatedConversation = try XCTUnwrap(storedConnection.to.oneOnOneConversation) + XCTAssertEqual(relatedConversation.remoteIdentifier, connection.qualifiedConversationID?.uuid) + + XCTAssertNil(relatedConversation.domain) + + XCTAssertTrue(relatedConversation.needsToBeUpdatedFromBackend) + } + } + + private enum Scaffolding { + nonisolated(unsafe) static let member1ID = WireDataModel.QualifiedID( + uuid: .mockID1, + domain: String.randomDomain() + ) + nonisolated(unsafe) static let conversationID = WireDataModel.QualifiedID( + uuid: .mockID2, + domain: String.randomDomain() + ) + nonisolated(unsafe) static let member2ID = WireDataModel.QualifiedID( + uuid: .mockID3, + domain: String.randomDomain() + ) + static let lastUpdate = Date() + static let connectionStatus = ZMConnectionStatus.accepted + + static let connection = ConnectionInfo( + senderID: Scaffolding.member1ID.uuid, + receiverID: Scaffolding.member2ID.uuid, + receiverQualifiedID: Scaffolding.member2ID, + conversationID: Scaffolding.conversationID.uuid, + qualifiedConversationID: Scaffolding.conversationID, + lastUpdate: Scaffolding.lastUpdate, + status: Scaffolding.connectionStatus + ) + } + +} diff --git a/WireDomain/Tests/WireDomainTests/LocalStores/ConversationLabelsLocalStoreTests.swift b/WireDomain/Tests/WireDomainTests/LocalStores/ConversationLabelsLocalStoreTests.swift new file mode 100644 index 00000000000..facb43d1475 --- /dev/null +++ b/WireDomain/Tests/WireDomainTests/LocalStores/ConversationLabelsLocalStoreTests.swift @@ -0,0 +1,310 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModelSupport +import WireTestingPackage +import XCTest +@testable import WireDataModel +@testable import WireDomain + +final class ConversationLabelsLocalStoreTests: XCTestCase { + + private var sut: ConversationLabelsLocalStore! + private var stack: CoreDataStack! + private var coreDataStackHelper: CoreDataStackHelper! + private var modelHelper: ModelHelper! + + private var conversation1: ZMConversation! + private var conversation2: ZMConversation! + private var conversation3: ZMConversation! + + private var context: NSManagedObjectContext { + stack.syncContext + } + + override func setUp() async throws { + modelHelper = ModelHelper() + coreDataStackHelper = CoreDataStackHelper() + /// Batch requests don't work with in-memory store + /// so we need to use a persistent store. + stack = try await coreDataStackHelper.createStack(inMemoryStore: false) + await cleanUpEntity() + await setupConversations() + + sut = ConversationLabelsLocalStore( + context: context + ) + } + + override func tearDown() async throws { + try coreDataStackHelper.cleanupDirectory() + coreDataStackHelper = nil + sut = nil + modelHelper = nil + } + + // MARK: - Tests + + func testStoreLabel_Given_Local_Store_Empty_It_Creates_Label_Locally() async throws { + // Mock + + let conversationLabel = Scaffolding.conversationLabel1 + + // When + + try await sut.storeLabel(conversationLabel) + + // Then + + try await context.perform { [context] in + let fetchRequest = NSFetchRequest @@ -127,6 +130,9 @@ + + diff --git a/wire-ios-mono.xcworkspace/xcshareddata/swiftpm/Package.resolved b/wire-ios-mono.xcworkspace/xcshareddata/swiftpm/Package.resolved index f317341169f..3ee62e58e0b 100644 --- a/wire-ios-mono.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/wire-ios-mono.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -73,15 +73,6 @@ "version": "6.0.1" } }, - { - "package": "Inject", - "repositoryURL": "https://github.com/krzysztofzablocki/Inject", - "state": { - "branch": null, - "revision": "728c56639ecb3df441d51d5bc6747329afabcfc9", - "version": "1.5.2" - } - }, { "package": "opentelemetry-swift", "repositoryURL": "https://github.com/DataDog/opentelemetry-swift-packages.git", diff --git a/WireAPI/.swiftpm/xcode/xcshareddata/xcschemes/WireAPI.xcscheme b/wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireAPIAll.xcscheme similarity index 61% rename from WireAPI/.swiftpm/xcode/xcshareddata/xcschemes/WireAPI.xcscheme rename to wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireAPIAll.xcscheme index 3b9d8816fb1..0c37f1232b0 100644 --- a/WireAPI/.swiftpm/xcode/xcshareddata/xcschemes/WireAPI.xcscheme +++ b/wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireAPIAll.xcscheme @@ -1,12 +1,40 @@ + + + + + + + + + ReferencedContainer = "container:WireAPI"> @@ -30,20 +58,19 @@ shouldUseLaunchSchemeArgsEnv = "YES"> + skipped = "NO"> + ReferencedContainer = "container:WireAPI"> @@ -68,10 +95,10 @@ + BlueprintIdentifier = "WireAPITests" + BuildableName = "WireAPITests" + BlueprintName = "WireAPITests" + ReferencedContainer = "container:WireAPI"> diff --git a/wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireAnalyticsAll.xcscheme b/wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireAnalyticsAll.xcscheme new file mode 100644 index 00000000000..36637a7d24d --- /dev/null +++ b/wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireAnalyticsAll.xcscheme @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WireFoundation/.swiftpm/xcode/xcshareddata/xcschemes/WireFoundation-Package.xcscheme b/wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireFoundationAll.xcscheme similarity index 75% rename from WireFoundation/.swiftpm/xcode/xcshareddata/xcschemes/WireFoundation-Package.xcscheme rename to wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireFoundationAll.xcscheme index cc33a74cb3d..9dcfe280a1d 100644 --- a/WireFoundation/.swiftpm/xcode/xcshareddata/xcschemes/WireFoundation-Package.xcscheme +++ b/wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireFoundationAll.xcscheme @@ -1,6 +1,6 @@ + BlueprintIdentifier = "WireLogging" + BuildableName = "WireLogging" + BlueprintName = "WireLogging" + ReferencedContainer = "container:WireFoundation"> + BlueprintIdentifier = "WireTestingPackage" + BuildableName = "WireTestingPackage" + BlueprintName = "WireTestingPackage" + ReferencedContainer = "container:WireFoundation"> + BlueprintIdentifier = "WireFoundation" + BuildableName = "WireFoundation" + BlueprintName = "WireFoundation" + ReferencedContainer = "container:WireFoundation"> + + + + + BlueprintIdentifier = "WireFoundationTests" + BuildableName = "WireFoundationTests" + BlueprintName = "WireFoundationTests" + ReferencedContainer = "container:WireFoundation"> @@ -72,32 +86,10 @@ shouldUseLaunchSchemeArgsEnv = "YES"> - - - - - - - - - - + BlueprintIdentifier = "WireTestingPackage" + BuildableName = "WireTestingPackage" + BlueprintName = "WireTestingPackage" + ReferencedContainer = "container:WireFoundation"> diff --git a/WireAnalytics/.swiftpm/xcode/xcshareddata/xcschemes/WireAnalytics.xcscheme b/wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireLoggingAll.xcscheme similarity index 80% rename from WireAnalytics/.swiftpm/xcode/xcshareddata/xcschemes/WireAnalytics.xcscheme rename to wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireLoggingAll.xcscheme index 281c9210b2f..4c214886766 100644 --- a/WireAnalytics/.swiftpm/xcode/xcshareddata/xcschemes/WireAnalytics.xcscheme +++ b/wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireLoggingAll.xcscheme @@ -1,6 +1,6 @@ + BlueprintIdentifier = "WireLogging" + BuildableName = "WireLogging" + BlueprintName = "WireLogging" + ReferencedContainer = "container:WireLogging"> @@ -30,7 +30,7 @@ shouldUseLaunchSchemeArgsEnv = "YES"> @@ -55,10 +55,10 @@ + BlueprintIdentifier = "WireLogging" + BuildableName = "WireLogging" + BlueprintName = "WireLogging" + ReferencedContainer = "container:WireLogging"> diff --git a/WireUI/.swiftpm/xcode/xcshareddata/xcschemes/WireUI-Package.xcscheme b/wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireUIAll.xcscheme similarity index 66% rename from WireUI/.swiftpm/xcode/xcshareddata/xcschemes/WireUI-Package.xcscheme rename to wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireUIAll.xcscheme index e1a3792b1fd..0c53c8daa8a 100644 --- a/WireUI/.swiftpm/xcode/xcshareddata/xcschemes/WireUI-Package.xcscheme +++ b/wire-ios-mono.xcworkspace/xcshareddata/xcschemes/WireUIAll.xcscheme @@ -1,6 +1,6 @@ + BlueprintIdentifier = "WireMoveToFolderUI" + BuildableName = "WireMoveToFolderUI" + BlueprintName = "WireMoveToFolderUI" + ReferencedContainer = "container:WireUI"> + BlueprintIdentifier = "WireSidebarUI" + BuildableName = "WireSidebarUI" + BlueprintName = "WireSidebarUI" + ReferencedContainer = "container:WireUI"> + BlueprintIdentifier = "WireReusableUIComponents" + BuildableName = "WireReusableUIComponents" + BlueprintName = "WireReusableUIComponents" + ReferencedContainer = "container:WireUI"> + + + + + + + + + + + + + + + + + ReferencedContainer = "container:WireUI"> + BlueprintIdentifier = "WireMoveToFolderUISupport" + BuildableName = "WireMoveToFolderUISupport" + BlueprintName = "WireMoveToFolderUISupport" + ReferencedContainer = "container:WireUI"> + BlueprintIdentifier = "WireMoveToFolderUITests" + BuildableName = "WireMoveToFolderUITests" + BlueprintName = "WireMoveToFolderUITests" + ReferencedContainer = "container:WireUI"> + BlueprintIdentifier = "WireReusableUIComponentsTests" + BuildableName = "WireReusableUIComponentsTests" + BlueprintName = "WireReusableUIComponentsTests" + ReferencedContainer = "container:WireUI"> + BlueprintIdentifier = "WireSidebarUITests" + BuildableName = "WireSidebarUITests" + BlueprintName = "WireSidebarUITests" + ReferencedContainer = "container:WireUI"> + ReferencedContainer = "container:WireUI"> + BlueprintIdentifier = "WireSettingsUI" + BuildableName = "WireSettingsUI" + BlueprintName = "WireSettingsUI" + ReferencedContainer = "container:WireUI"> - - - - - - - - - + + BlueprintIdentifier = "WireConversationListUI" + BuildableName = "WireConversationListUI" + BlueprintName = "WireConversationListUI" + ReferencedContainer = "container:WireUI"> - - + + + ReferencedContainer = "container:WireUI"> - - - - - - - - - - + + + BlueprintIdentifier = "WireDesign" + BuildableName = "WireDesign" + BlueprintName = "WireDesign" + ReferencedContainer = "container:WireUI"> - - + + + BlueprintIdentifier = "WireAccountImageUITests" + BuildableName = "WireAccountImageUITests" + BlueprintName = "WireAccountImageUITests" + ReferencedContainer = "container:WireUI"> - - + + + BlueprintIdentifier = "WireIndividualToTeamMigrationUITests" + BuildableName = "WireIndividualToTeamMigrationUITests" + BlueprintName = "WireIndividualToTeamMigrationUITests" + ReferencedContainer = "container:WireUI"> - - + + + BlueprintIdentifier = "WireIndividualToTeamMigrationUI" + BuildableName = "WireIndividualToTeamMigrationUI" + BlueprintName = "WireIndividualToTeamMigrationUI" + ReferencedContainer = "container:WireUI"> - - + + + + + + + + + BlueprintIdentifier = "WireMoveToFolderUI" + BuildableName = "WireMoveToFolderUI" + BlueprintName = "WireMoveToFolderUI" + ReferencedContainer = "container:WireUI"> diff --git a/wire-ios-notification-engine/LICENSE b/wire-ios-notification-engine/LICENSE deleted file mode 100644 index ebd853e83a2..00000000000 --- a/wire-ios-notification-engine/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - -Copyright (C) 2007 Free Software Foundation, Inc. -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - -{one line to give the program's name and a brief idea of what it does.} -Copyright (C) {year} {name of author} - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -{project} Copyright (C) {year} {fullname} -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - -The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/wire-ios-notification-engine/README.md b/wire-ios-notification-engine/README.md deleted file mode 100644 index 5b56b3d9a96..00000000000 --- a/wire-ios-notification-engine/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Wire™ - -[![Wire logo](https://github.com/wireapp/wire/blob/master/assets/header-small.png?raw=true)](https://wire.com/jobs/) - -This repository is part of the source code of Wire. You can find more information at [wire.com](https://wire.com) or by contacting opensource@wire.com. - -You can find the published source code at [github.com/wireapp/wire](https://github.com/wireapp/wire). - -For licensing information, see the attached LICENSE file and the list of third-party licenses at [wire.com/legal/licenses/](https://wire.com/legal/licenses/). - -# wire-ios-notification-engine - -This framework is part of Wire iOS. Additional documentation is available in the [Wire iOS wiki](https://github.com/wireapp/wire-ios/wiki). diff --git a/wire-ios-notification-engine/Sources/NotificationSession.swift b/wire-ios-notification-engine/Sources/NotificationSession.swift index 17b628f7e05..9507f441d50 100644 --- a/wire-ios-notification-engine/Sources/NotificationSession.swift +++ b/wire-ios-notification-engine/Sources/NotificationSession.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireRequestStrategy public enum NotificationSessionError: LocalizedError { @@ -317,7 +318,8 @@ public final class NotificationSession { coreDataStack.syncContext.proteusService = proteusService } - if DeveloperFlag.enableMLSSupport.isOn, coreDataStack.syncContext.mlsDecryptionService == nil { + let mlsFeature = FeatureRepository(context: coreDataStack.syncContext).fetchMLS() + if mlsFeature.isEnabled, coreDataStack.syncContext.mlsDecryptionService == nil { coreDataStack.syncContext.mlsDecryptionService = mlsDecryptionService } } diff --git a/wire-ios-notification-engine/Sources/Synchronization/Strategies/PushNotificationStrategy.swift b/wire-ios-notification-engine/Sources/Synchronization/Strategies/PushNotificationStrategy.swift index 0aba05f85e6..0a490bce03d 100644 --- a/wire-ios-notification-engine/Sources/Synchronization/Strategies/PushNotificationStrategy.swift +++ b/wire-ios-notification-engine/Sources/Synchronization/Strategies/PushNotificationStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireRequestStrategy protocol PushNotificationStrategyDelegate: AnyObject { diff --git a/wire-ios-notification-engine/Tests/TestPlans/AllTests.xctestplan b/wire-ios-notification-engine/Tests/TestPlans/AllTests.xctestplan new file mode 100644 index 00000000000..c01fbf7c5fd --- /dev/null +++ b/wire-ios-notification-engine/Tests/TestPlans/AllTests.xctestplan @@ -0,0 +1,26 @@ +{ + "configurations" : [ + { + "id" : "AB88FACB-0871-4EE4-8D50-8E4959B7ED42", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "language" : "en", + "region" : "DE", + "testExecutionOrdering" : "random" + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:WireNotificationEngine.xcodeproj", + "identifier" : "EE761664299E60C9005DB75F", + "name" : "WireNotificationEngineTests" + } + } + ], + "version" : 1 +} diff --git a/wire-ios-notification-engine/WireNotificationEngine.xcodeproj/project.pbxproj b/wire-ios-notification-engine/WireNotificationEngine.xcodeproj/project.pbxproj index d1492f1b693..7fcd56896aa 100644 --- a/wire-ios-notification-engine/WireNotificationEngine.xcodeproj/project.pbxproj +++ b/wire-ios-notification-engine/WireNotificationEngine.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -16,6 +16,7 @@ 591B6E262C8B097B009F8A7B /* WireRequestStrategy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE668BC52954BA4C00D939E7 /* WireRequestStrategy.framework */; }; 591B6E2C2C8B0983009F8A7B /* WireDataModelSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59B170E52BCE70E200575995 /* WireDataModelSupport.framework */; }; 591B6E2F2C8B0988009F8A7B /* WireMockTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEC80B5629B6054A00099727 /* WireMockTransport.framework */; }; + 59537D8D2CFF9FB300920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59537D8C2CFF9FB300920B59 /* WireLogging */; }; 630A582D29AF9F3E00E26C4D /* BaseNotificationSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 630A582C29AF9F3E00E26C4D /* BaseNotificationSessionTests.swift */; }; EE0BA28A29D59B1D004E93B5 /* NotificationSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0BA28929D59B1D004E93B5 /* NotificationSessionTests.swift */; }; EE3245FE28229E8600F2A84A /* ApplicationStatusDirectory.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3245FD28229E8600F2A84A /* ApplicationStatusDirectory.swift */; }; @@ -70,7 +71,6 @@ 06BD1D3B2487B01C002F82A6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59B170E52BCE70E200575995 /* WireDataModelSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireDataModelSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 630A582C29AF9F3E00E26C4D /* BaseNotificationSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNotificationSessionTests.swift; sourceTree = ""; }; - E6A09CD52C2EBA3E0069C15A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; EE0BA28929D59B1D004E93B5 /* NotificationSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSessionTests.swift; sourceTree = ""; }; EE3245FD28229E8600F2A84A /* ApplicationStatusDirectory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationStatusDirectory.swift; sourceTree = ""; }; EE3245FF28229F6B00F2A84A /* ClientRegistrationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientRegistrationStatus.swift; sourceTree = ""; }; @@ -89,12 +89,27 @@ EEC80B5629B6054A00099727 /* WireMockTransport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireMockTransport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 59D264212CF721B40005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + TestPlans/AllTests.xctestplan, + ); + target = 067ECB392487A93D00701956 /* WireNotificationEngine */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 59D2641F2CF721A00005317F /* Tests */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (59D264212CF721B40005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Tests; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 067ECB372487A93D00701956 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 591B6E262C8B097B009F8A7B /* WireRequestStrategy.framework in Frameworks */, + 59537D8D2CFF9FB300920B59 /* WireLogging in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -132,10 +147,10 @@ 0660428B247FE76A00A5F161 = { isa = PBXGroup; children = ( - E6A09CD52C2EBA3E0069C15A /* README.md */, EE9AEC972BD159C700F7853F /* WireNotificationEngine.docc */, 06BD1D2D2487B01C002F82A6 /* Resources */, 06604296247FE76A00A5F161 /* Sources */, + 59D2641F2CF721A00005317F /* Tests */, 067ECB3B2487A93D00701956 /* WireNotificationEngine */, EE761670299E614B005DB75F /* WireNotificationEngineTests */, EEC80B4129B6053200099727 /* WireNotificationEngineTestHost */, @@ -282,6 +297,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 59D2641F2CF721A00005317F /* Tests */, + ); name = WireNotificationEngine; productName = WireNotificationEngine; productReference = 067ECB3A2487A93D00701956 /* WireNotificationEngine.framework */; @@ -793,6 +811,13 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + 59537D8C2CFF9FB300920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 0660428C247FE76A00A5F161 /* Project object */; } diff --git a/wire-ios-notification-engine/WireNotificationEngine.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wire-ios-notification-engine/WireNotificationEngine.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000000..08de0be8d3c --- /dev/null +++ b/wire-ios-notification-engine/WireNotificationEngine.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + diff --git a/wire-ios-notification-engine/WireNotificationEngine.xcodeproj/xcshareddata/xcschemes/WireNotificationEngine.xcscheme b/wire-ios-notification-engine/WireNotificationEngine.xcodeproj/xcshareddata/xcschemes/WireNotificationEngine.xcscheme index d17991efbad..337c5a56c31 100644 --- a/wire-ios-notification-engine/WireNotificationEngine.xcodeproj/xcshareddata/xcschemes/WireNotificationEngine.xcscheme +++ b/wire-ios-notification-engine/WireNotificationEngine.xcodeproj/xcshareddata/xcschemes/WireNotificationEngine.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1610" + version = "1.7"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> - - - - - - - - - - + + + + - - - - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - -{one line to give the program's name and a brief idea of what it does.} -Copyright (C) {year} {name of author} - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -{project} Copyright (C) {year} {fullname} -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - -The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/wire-ios-protos/Protos/messages.pb.swift b/wire-ios-protos/Protos/messages.pb.swift index 42d9d8e1c7d..0a8030f17d0 100644 --- a/wire-ios-protos/Protos/messages.pb.swift +++ b/wire-ios-protos/Protos/messages.pb.swift @@ -26,23 +26,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - import Foundation import SwiftProtobuf @@ -321,8 +304,6 @@ public struct GenericMessage: Sendable { set {content = .dataTransfer(newValue)} } - /// UnknownStrategy unknownStrategy = 25; -- Defined outside the oneof - /// Next field should be 26 ↓ public var inCallEmoji: InCallEmoji { get { if case .inCallEmoji(let v)? = content {return v} @@ -331,6 +312,16 @@ public struct GenericMessage: Sendable { set {content = .inCallEmoji(newValue)} } + /// UnknownStrategy unknownStrategy = 25; -- Defined outside the oneof + /// Next field should be 26 ↓ + public var inCallHandRaise: InCallHandRaise { + get { + if case .inCallHandRaise(let v)? = content {return v} + return InCallHandRaise() + } + set {content = .inCallHandRaise(newValue)} + } + public var unknownStrategy: GenericMessage.UnknownStrategy { get {return _unknownStrategy ?? .ignore} set {_unknownStrategy = newValue} @@ -366,9 +357,10 @@ public struct GenericMessage: Sendable { case buttonActionConfirmation(ButtonActionConfirmation) /// client-side synchronization across devices of the same user case dataTransfer(DataTransfer) + case inCallEmoji(InCallEmoji) /// UnknownStrategy unknownStrategy = 25; -- Defined outside the oneof /// Next field should be 26 ↓ - case inCallEmoji(InCallEmoji) + case inCallHandRaise(InCallHandRaise) fileprivate var isInitialized: Bool { // The use of inline closures is to circumvent an issue where the compiler @@ -455,6 +447,10 @@ public struct GenericMessage: Sendable { guard case .dataTransfer(let v) = self else { preconditionFailure() } return v.isInitialized }() + case .inCallHandRaise: return { + guard case .inCallHandRaise(let v) = self else { preconditionFailure() } + return v.isInitialized + }() default: return true } } @@ -2318,6 +2314,28 @@ public struct InCallEmoji: Sendable { public init() {} } +public struct InCallHandRaise: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// true if the hand is raised, false if lowered + public var isHandUp: Bool { + get {return _isHandUp ?? false} + set {_isHandUp = newValue} + } + /// Returns true if `isHandUp` has been explicitly set. + public var hasIsHandUp: Bool {return self._isHandUp != nil} + /// Clears the value of `isHandUp`. Subsequent reads from it will return its default value. + public mutating func clearIsHandUp() {self._isHandUp = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _isHandUp: Bool? = nil +} + public struct Calling: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -2440,6 +2458,7 @@ extension GenericMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement 22: .same(proto: "buttonActionConfirmation"), 23: .same(proto: "dataTransfer"), 24: .same(proto: "inCallEmoji"), + 26: .same(proto: "inCallHandRaise"), 25: .same(proto: "unknownStrategy"), ] @@ -2738,6 +2757,19 @@ extension GenericMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement } }() case 25: try { try decoder.decodeSingularEnumField(value: &self._unknownStrategy) }() + case 26: try { + var v: InCallHandRaise? + var hadOneofValue = false + if let current = self.content { + hadOneofValue = true + if case .inCallHandRaise(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.content = .inCallHandRaise(v) + } + }() default: break } } @@ -2840,11 +2872,14 @@ extension GenericMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement guard case .inCallEmoji(let v)? = self.content else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 24) }() - case nil: break + default: break } try { if let v = self._unknownStrategy { try visitor.visitSingularEnumField(value: v, fieldNumber: 25) } }() + try { if case .inCallHandRaise(let v)? = self.content { + try visitor.visitSingularMessageField(value: v, fieldNumber: 26) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -5106,6 +5141,47 @@ extension InCallEmoji: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati } } +extension InCallHandRaise: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "InCallHandRaise" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "is_hand_up"), + ] + + public var isInitialized: Bool { + if self._isHandUp == nil {return false} + return true + } + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBoolField(value: &self._isHandUp) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._isHandUp { + try visitor.visitSingularBoolField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: InCallHandRaise, rhs: InCallHandRaise) -> Bool { + if lhs._isHandUp != rhs._isHandUp {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Calling: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "Calling" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ diff --git a/wire-ios-protos/Protos/mls.pb.swift b/wire-ios-protos/Protos/mls.pb.swift index 1748ffc6c34..e989f3ee7ba 100644 --- a/wire-ios-protos/Protos/mls.pb.swift +++ b/wire-ios-protos/Protos/mls.pb.swift @@ -26,23 +26,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - import Foundation import SwiftProtobuf diff --git a/wire-ios-protos/Protos/otr.pb.swift b/wire-ios-protos/Protos/otr.pb.swift index a0c8137d922..c0d547a844c 100644 --- a/wire-ios-protos/Protos/otr.pb.swift +++ b/wire-ios-protos/Protos/otr.pb.swift @@ -26,23 +26,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - import Foundation import SwiftProtobuf diff --git a/WireFoundation/.swiftpm/WireUtilities.xctestplan b/wire-ios-protos/ProtosTests/TestPlans/AllTests.xctestplan similarity index 53% rename from WireFoundation/.swiftpm/WireUtilities.xctestplan rename to wire-ios-protos/ProtosTests/TestPlans/AllTests.xctestplan index c8616a17dea..7762dd9144e 100644 --- a/WireFoundation/.swiftpm/WireUtilities.xctestplan +++ b/wire-ios-protos/ProtosTests/TestPlans/AllTests.xctestplan @@ -1,8 +1,8 @@ { "configurations" : [ { - "id" : "F6B6F291-6B7B-42FB-B7DE-25C4FA59F89D", - "name" : "Test Scheme Action", + "id" : "ABAAC225-C137-4281-A5E6-334A914925C4", + "name" : "Configuration 1", "options" : { } @@ -16,9 +16,9 @@ "testTargets" : [ { "target" : { - "containerPath" : "container:", - "identifier" : "WireUtilitiesPackageTests", - "name" : "WireUtilitiesPackageTests" + "containerPath" : "container:WireProtos.xcodeproj", + "identifier" : "090838081B872DBF00629095", + "name" : "WireProtosTests" } } ], diff --git a/wire-ios-protos/README.md b/wire-ios-protos/README.md deleted file mode 100644 index a96c835f7a7..00000000000 --- a/wire-ios-protos/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Wire™ - -[![Wire logo](https://github.com/wireapp/wire/blob/master/assets/header-small.png?raw=true)](https://wire.com/jobs/) - -This repository is part of the source code of Wire. You can find more information at [wire.com](https://wire.com) or by contacting opensource@wire.com. - -You can find the published source code at [github.com/wireapp/wire](https://github.com/wireapp/wire). - -For licensing information, see the attached LICENSE file and the list of third-party licenses at [wire.com/legal/licenses/](https://wire.com/legal/licenses/). - -# wire-ios-protos - -This framework is part of Wire iOS SyncEngine. Additional documentation is available in the [Wire iOS wiki](https://github.com/wireapp/wire-ios/wiki). - -The wire-ios-protos framework contains precompiled protocol buffer definitions for Swift. - -## How to build - -This framework is using Carthage to manage its dependencies. To pull the dependencies binaries, run `carthage bootstrap --platform ios --use-xcframeworks --no-use-binaries`. - -You need the Swift Protocol Buffer compiler to build the protobuf Swift files. Run `brew install swift-protobuf` to install it. - -From the `wire-ios-protos` directory, run `bash Scripts/compile-protos.sh` to generate the files from the protobuf definitions imported from Carthage. - -You can now open the Xcode project and build. - -## Troubleshooting - -``` -> bash Scripts/compile-swift-protos.sh - File "Scripts/find-carthage.py", line 30 - print("No Carthage folder found", file=sys.stderr) - ^ -SyntaxError: invalid syntax -``` - -If you encounter this error, you may need to update python to version 3 diff --git a/wire-ios-protos/WireProtos.docc/WireProtos.md b/wire-ios-protos/WireProtos.docc/WireProtos.md index c02f7269f16..25fafdea2ce 100644 --- a/wire-ios-protos/WireProtos.docc/WireProtos.md +++ b/wire-ios-protos/WireProtos.docc/WireProtos.md @@ -6,5 +6,14 @@ Defines Swift models for shared Protobuf data structures. WireProtos contains the generated Swift data structures for the Profobuf data structures that are shared across all Wire platforms. +### How to update protobufs + +1. Have protobuf installed: `brew install protobuf; brew install swift-protobuf` +2. `cd wire-ios-protos` +3. `./Scripts/compile-swift-protos.sh` +4. remove duplicate header (to be fixed) +5. commit the `*.pb.swift` files + ## Topics + diff --git a/wire-ios-protos/WireProtos.xcodeproj/project.pbxproj b/wire-ios-protos/WireProtos.xcodeproj/project.pbxproj index ade265a80f2..d422801cbdc 100644 --- a/wire-ios-protos/WireProtos.xcodeproj/project.pbxproj +++ b/wire-ios-protos/WireProtos.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -30,7 +30,6 @@ /* Begin PBXFileReference section */ 016BDDED2A9F86E50054FB04 /* Scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Scripts; sourceTree = ""; }; - 016BDDEE2A9F88AF0054FB04 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 090837FE1B872DBF00629095 /* WireProtos.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WireProtos.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 090838021B872DBF00629095 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 090838091B872DBF00629095 /* WireProtosTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WireProtosTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -57,6 +56,20 @@ F1FDF2DF21B043F400E037A1 /* WireProtos.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WireProtos.h; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 59D264302CF724FB0005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + AllTests.xctestplan, + ); + target = 090838081B872DBF00629095 /* WireProtosTests */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 59D2642F2CF724F80005317F /* TestPlans */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (59D264302CF724FB0005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TestPlans; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 090837FA1B872DBF00629095 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -81,7 +94,6 @@ isa = PBXGroup; children = ( EE9AEC8F2BD1594600F7853F /* WireProtos.docc */, - 016BDDEE2A9F88AF0054FB04 /* README.md */, 016BDDED2A9F86E50054FB04 /* Scripts */, 09B70B4B1B874390002667A6 /* Resources */, 090838001B872DBF00629095 /* Protos */, @@ -130,6 +142,7 @@ 0908380D1B872DBF00629095 /* ProtosTests */ = { isa = PBXGroup; children = ( + 59D2642F2CF724F80005317F /* TestPlans */, 09BCDB6D1BCBE08D0020DCC7 /* medium.jpg */, 0908380E1B872DBF00629095 /* Supporting Files */, 5E13807F21A30AB7004A646C /* ProtosTests.swift */, @@ -233,6 +246,9 @@ dependencies = ( 0908380C1B872DBF00629095 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 59D2642F2CF724F80005317F /* TestPlans */, + ); name = WireProtosTests; productName = ProtosTests; productReference = 090838091B872DBF00629095 /* WireProtosTests.xctest */; diff --git a/wire-ios-protos/WireProtos.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/wire-ios-protos/WireProtos.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 6c5bfba24a8..919434a6254 100644 --- a/wire-ios-protos/WireProtos.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/wire-ios-protos/WireProtos.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/wire-ios-protos/WireProtos.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wire-ios-protos/WireProtos.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 3ddf867a10a..dc8d12300fe 100644 --- a/wire-ios-protos/WireProtos.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/wire-ios-protos/WireProtos.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -4,5 +4,7 @@ BuildSystemType Latest + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + diff --git a/wire-ios-protos/WireProtos.xcodeproj/xcshareddata/xcschemes/WireProtos.xcscheme b/wire-ios-protos/WireProtos.xcodeproj/xcshareddata/xcschemes/WireProtos.xcscheme index 4d6136b38b5..6db1ec0c0d1 100644 --- a/wire-ios-protos/WireProtos.xcodeproj/xcshareddata/xcschemes/WireProtos.xcscheme +++ b/wire-ios-protos/WireProtos.xcodeproj/xcshareddata/xcschemes/WireProtos.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1610" + version = "1.7"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> - - - - - - - - + + + + @@ -73,15 +57,6 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - - - - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - -{one line to give the program's name and a brief idea of what it does.} -Copyright (C) {year} {name of author} - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -{project} Copyright (C) {year} {fullname} -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - -The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/wire-ios-request-strategy/README.md b/wire-ios-request-strategy/README.md deleted file mode 100644 index 4d820226798..00000000000 --- a/wire-ios-request-strategy/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Wire™ - -[![Wire logo](https://github.com/wireapp/wire/blob/master/assets/header-small.png?raw=true)](https://wire.com/jobs/) - -[![Azure Pipelines Build Status](https://dev.azure.com/wireswiss/Wire%20iOS/_apis/build/status/Frameworks/wire-ios-request-strategy?branchName=develop)](https://dev.azure.com/wireswiss/Wire%20iOS/_build/latest?definitionId=30&branchName=develop) [![codecov](https://codecov.io/gh/wireapp/wire-ios-request-strategy/branch/develop/graph/badge.svg)](https://codecov.io/gh/wireapp/wire-ios-request-strategy) - -This repository is part of the source code of Wire. You can find more information at [wire.com](https://wire.com) or by contacting opensource@wire.com. - -You can find the published source code at [github.com/wireapp/wire](https://github.com/wireapp/wire). - -For licensing information, see the attached LICENSE file and the list of third-party licenses at [wire.com/legal/licenses/](https://wire.com/legal/licenses/). - -# wire-ios-request-strategy - -This framework is part of Wire iOS stack. Additional documentation is available in the [Wire iOS wiki](https://github.com/wireapp/wire-ios/wiki). diff --git a/wire-ios-request-strategy/Sources/APIs/APIProvider.swift b/wire-ios-request-strategy/Sources/APIs/APIProvider.swift index 2f669d750d6..48ca83d62a7 100644 --- a/wire-ios-request-strategy/Sources/APIs/APIProvider.swift +++ b/wire-ios-request-strategy/Sources/APIs/APIProvider.swift @@ -43,6 +43,7 @@ public struct APIProvider: APIProviderInterface { case .v4: PrekeyAPIV4(httpClient: httpClient) case .v5: PrekeyAPIV5(httpClient: httpClient) case .v6: PrekeyAPIV6(httpClient: httpClient) + case .v7: PrekeyAPIV7(httpClient: httpClient) } } @@ -55,6 +56,7 @@ public struct APIProvider: APIProviderInterface { case .v4: MessageAPIV4(httpClient: httpClient) case .v5: MessageAPIV5(httpClient: httpClient) case .v6: MessageAPIV6(httpClient: httpClient) + case .v7: MessageAPIV7(httpClient: httpClient) } } @@ -63,6 +65,7 @@ public struct APIProvider: APIProviderInterface { case .v0, .v1, .v2, .v3, .v4: nil case .v5: E2eIAPIV5(httpClient: httpClient) case .v6: E2eIAPIV6(httpClient: httpClient) + case .v7: E2eIAPIV7(httpClient: httpClient) } } @@ -75,6 +78,7 @@ public struct APIProvider: APIProviderInterface { case .v4: UserClientAPIV4(httpClient: httpClient) case .v5: UserClientAPIV5(httpClient: httpClient) case .v6: UserClientAPIV6(httpClient: httpClient) + case .v7: UserClientAPIV7(httpClient: httpClient) } } } diff --git a/wire-ios-request-strategy/Sources/APIs/E2eIAPI.swift b/wire-ios-request-strategy/Sources/APIs/E2eIAPI.swift index b984e98e406..25c6ce92768 100644 --- a/wire-ios-request-strategy/Sources/APIs/E2eIAPI.swift +++ b/wire-ios-request-strategy/Sources/APIs/E2eIAPI.swift @@ -78,6 +78,10 @@ class E2eIAPIV6: E2eIAPIV5 { } } +final class E2eIAPIV7: E2eIAPIV6 { + override var apiVersion: APIVersion { .v7 } +} + private enum Constant { static let pathClients = "clients" static let pathNonce = "nonce" diff --git a/wire-ios-request-strategy/Sources/APIs/MessageAPI.swift b/wire-ios-request-strategy/Sources/APIs/MessageAPI.swift index fc9541815d9..a477bd4980d 100644 --- a/wire-ios-request-strategy/Sources/APIs/MessageAPI.swift +++ b/wire-ios-request-strategy/Sources/APIs/MessageAPI.swift @@ -378,3 +378,7 @@ class MessageAPIV6: MessageAPIV5 { .v6 } } + +final class MessageAPIV7: MessageAPIV6 { + override var apiVersion: APIVersion { .v7 } +} diff --git a/wire-ios-request-strategy/Sources/APIs/PrekeyAPI.swift b/wire-ios-request-strategy/Sources/APIs/PrekeyAPI.swift index 325cf1627ec..9bd0cb70edc 100644 --- a/wire-ios-request-strategy/Sources/APIs/PrekeyAPI.swift +++ b/wire-ios-request-strategy/Sources/APIs/PrekeyAPI.swift @@ -37,7 +37,7 @@ class PrekeyAPIV0: PrekeyAPI { self.httpClient = httpClient } - open var apiVersion: APIVersion { + var apiVersion: APIVersion { .v0 } @@ -140,6 +140,10 @@ class PrekeyAPIV6: PrekeyAPIV5 { } } +final class PrekeyAPIV7: PrekeyAPIV6 { + override var apiVersion: APIVersion { .v7 } +} + extension Collection { var clientListByUserID: Payload.ClientListByUserID { diff --git a/wire-ios-request-strategy/Sources/APIs/UserClientAPI.swift b/wire-ios-request-strategy/Sources/APIs/UserClientAPI.swift index fb572e0ab97..d5c907b6eb4 100644 --- a/wire-ios-request-strategy/Sources/APIs/UserClientAPI.swift +++ b/wire-ios-request-strategy/Sources/APIs/UserClientAPI.swift @@ -88,3 +88,7 @@ class UserClientAPIV6: UserClientAPIV5 { .v6 } } + +final class UserClientAPIV7: UserClientAPIV6 { + override var apiVersion: APIVersion { .v7 } +} diff --git a/wire-ios-request-strategy/Sources/E2EIdentity/E2EIEnrollment.swift b/wire-ios-request-strategy/Sources/E2EIdentity/E2EIEnrollment.swift index 04f76dcc891..5160897763a 100644 --- a/wire-ios-request-strategy/Sources/E2EIdentity/E2EIEnrollment.swift +++ b/wire-ios-request-strategy/Sources/E2EIdentity/E2EIEnrollment.swift @@ -18,6 +18,7 @@ import Foundation import WireCoreCrypto +import WireLogging public protocol E2EIEnrollmentInterface { diff --git a/wire-ios-request-strategy/Sources/E2EIdentity/E2EIKeyPackageRotator.swift b/wire-ios-request-strategy/Sources/E2EIdentity/E2EIKeyPackageRotator.swift index eec5933449b..3cbf5f79c18 100644 --- a/wire-ios-request-strategy/Sources/E2EIdentity/E2EIKeyPackageRotator.swift +++ b/wire-ios-request-strategy/Sources/E2EIdentity/E2EIKeyPackageRotator.swift @@ -20,6 +20,7 @@ import Combine import Foundation import WireCoreCrypto import WireDataModel +import WireLogging // sourcery: AutoMockable public protocol E2EIKeyPackageRotating { diff --git a/wire-ios-request-strategy/Sources/E2EIdentity/E2EIRepository.swift b/wire-ios-request-strategy/Sources/E2EIdentity/E2EIRepository.swift index e28dea1edab..162f728fbbd 100644 --- a/wire-ios-request-strategy/Sources/E2EIdentity/E2EIRepository.swift +++ b/wire-ios-request-strategy/Sources/E2EIdentity/E2EIRepository.swift @@ -19,6 +19,7 @@ import Combine import Foundation import WireCoreCrypto +import WireLogging public protocol E2EIRepositoryInterface { diff --git a/wire-ios-request-strategy/Sources/E2EIdentity/EnrollE2EICertificateUseCase.swift b/wire-ios-request-strategy/Sources/E2EIdentity/EnrollE2EICertificateUseCase.swift index 72576b0ad39..a2d077bf0ed 100644 --- a/wire-ios-request-strategy/Sources/E2EIdentity/EnrollE2EICertificateUseCase.swift +++ b/wire-ios-request-strategy/Sources/E2EIdentity/EnrollE2EICertificateUseCase.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public struct OAuthParameters { diff --git a/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactory.swift b/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactory.swift index 3c65810e87c..e124ef53434 100644 --- a/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactory.swift +++ b/wire-ios-request-strategy/Sources/Helpers/AssetRequestFactory.swift @@ -73,7 +73,7 @@ public final class AssetRequestFactory: NSObject { case .v0, .v1: "/assets/v3" - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: "/assets" } @@ -109,7 +109,7 @@ public final class AssetRequestFactory: NSObject { case .v0, .v1: "/assets/v3" - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: "/assets" } diff --git a/wire-ios-request-strategy/Sources/Helpers/EventDecoderDecryptionTests.swift b/wire-ios-request-strategy/Sources/Helpers/EventDecoderDecryptionTests.swift index 83924b976f8..03905b4e0c4 100644 --- a/wire-ios-request-strategy/Sources/Helpers/EventDecoderDecryptionTests.swift +++ b/wire-ios-request-strategy/Sources/Helpers/EventDecoderDecryptionTests.swift @@ -17,6 +17,7 @@ // import Foundation +import WireCoreCrypto import WireCryptobox import WireDataModel import WireProtos @@ -103,7 +104,7 @@ final class EventDecoderDecryptionTests: MessagingTestBase { "recipient": self.selfClient.remoteIdentifier!, "sender": self.otherClient.remoteIdentifier!, "id": UUID.create().transportString(), - "key": Data("bah".utf8).base64String() + "text": Data("bah".utf8).base64String() ] let payload = [ @@ -122,15 +123,9 @@ final class EventDecoderDecryptionTests: MessagingTestBase { } // WHEN - disableZMLogError(true) - let keystore = await syncMOC.perform { self.syncMOC.zm_cryptKeyStore } - let unwrappedKeyStore = try XCTUnwrap(keystore) - await unwrappedKeyStore.encryptionContext.performAsync { session in - _ = await sut.decryptProteusEventAndAddClient(event, in: self.syncMOC) { sessionID, encryptedData in - try session.decryptData(encryptedData, for: sessionID.mapToEncryptionSessionID()) - } + _ = await sut.decryptProteusEventAndAddClient(event, in: syncMOC) { _, _ in + throw ProteusService.DecryptionError.failedToEstablishSessionFromMessage(.SessionNotFound) } - disableZMLogError(false) await syncMOC.perform { // THEN @@ -141,104 +136,4 @@ final class EventDecoderDecryptionTests: MessagingTestBase { } } - func testThatItInsertsAnUnableToDecryptMessageIfTheEncryptedPayloadIsLongerThan_18_000() async throws { - // Given - let lastEventIDRepository = MockLastEventIDRepositoryInterface() - let sut = EventDecoder(eventMOC: eventMOC, syncMOC: syncMOC, lastEventIDRepository: lastEventIDRepository) - let crlf = "\u{0000}\u{0001}\u{0000}\u{000D}\u{0000A}" - let text = "https://wir\("".padding(toLength: crlf.count * 20_000, withPad: crlf, startingAt: 0))e.com/" - XCTAssertGreaterThan(text.count, 18_000) - let message = GenericMessage(content: Text(content: text)) - - let wrapper = await syncMOC.perform { - NSDictionary(dictionary: [ - "id": UUID.create().transportString(), - "payload": [ - [ - "type": "conversation.otr-message-add", - "from": self.otherUser.remoteIdentifier!.transportString(), - "conversation": self.groupConversation.remoteIdentifier!.transportString(), - "time": Date().transportString(), - "data": [ - "recipient": self.selfClient.remoteIdentifier!, - "sender": self.otherClient.remoteIdentifier!, - "text": self.encryptedMessageToSelf(message: message, from: self.otherClient).base64String() - ] - ] - ] - ]) - } - - let event = try XCTUnwrap(ZMUpdateEvent.eventsArray(from: wrapper, source: .download)?.first) - - // When - disableZMLogError(true) - let keystore = await syncMOC.perform { self.syncMOC.zm_cryptKeyStore } - let unwrappedKeyStore = try XCTUnwrap(keystore) - await unwrappedKeyStore.encryptionContext.performAsync { session in - _ = await sut.decryptProteusEventAndAddClient(event, in: self.syncMOC) { sessionID, encryptedData in - try session.decryptData(encryptedData, for: sessionID.mapToEncryptionSessionID()) - } - } - disableZMLogError(false) - - // Then - await syncMOC.perform { - guard let lastMessage = self.groupConversation.lastMessage as? ZMSystemMessage else { - return XCTFail("Last conversation message is not a system message") - } - XCTAssertEqual(lastMessage.systemMessageType, .decryptionFailed) - } - } - - func testThatItInsertsAnUnableToDecryptMessageIfTheEncryptedPayloadIsLongerThan_18_000_External_Message( - ) async throws { - // Given - let lastEventIDRepository = MockLastEventIDRepositoryInterface() - let sut = EventDecoder(eventMOC: eventMOC, syncMOC: syncMOC, lastEventIDRepository: lastEventIDRepository) - let crlf = "\u{0000}\u{0001}\u{0000}\u{000D}\u{0000A}" - let text = "https://wir\("".padding(toLength: crlf.count * 20_000, withPad: crlf, startingAt: 0))e.com/" - XCTAssertGreaterThan(text.count, 18_000) - - let wrapper = await syncMOC.perform { - NSDictionary(dictionary: [ - "id": UUID.create().transportString(), - "payload": [ - [ - "type": "conversation.otr-message-add", - "from": self.otherUser.remoteIdentifier!.transportString(), - "conversation": self.groupConversation.remoteIdentifier!.transportString(), - "time": Date().transportString(), - "data": [ - "data": text, - "recipient": self.selfClient.remoteIdentifier!, - "sender": self.otherClient.remoteIdentifier!, - "text": Data("something with less than 18000 characters count".utf8).base64String() - ] - ] - ] - ]) - } - - let event = try XCTUnwrap(ZMUpdateEvent.eventsArray(from: wrapper, source: .download)?.first) - - // When - disableZMLogError(true) - let keystore = await syncMOC.perform { self.syncMOC.zm_cryptKeyStore } - let unwrappedKeyStore = try XCTUnwrap(keystore) - await unwrappedKeyStore.encryptionContext.performAsync { session in - _ = await sut.decryptProteusEventAndAddClient(event, in: self.syncMOC) { sessionID, encryptedData in - try session.decryptData(encryptedData, for: sessionID.mapToEncryptionSessionID()) - } - } - disableZMLogError(false) - - // Then - await syncMOC.perform { - guard let lastMessage = self.groupConversation.lastMessage as? ZMSystemMessage else { - return XCTFail("Last conversation message is not a system message") - } - XCTAssertEqual(lastMessage.systemMessageType, .decryptionFailed) - } - } } diff --git a/wire-ios-request-strategy/Sources/Helpers/MessageExpirationTimer.swift b/wire-ios-request-strategy/Sources/Helpers/MessageExpirationTimer.swift index 6ee3cfa9a08..aa3e3ef2b97 100644 --- a/wire-ios-request-strategy/Sources/Helpers/MessageExpirationTimer.swift +++ b/wire-ios-request-strategy/Sources/Helpers/MessageExpirationTimer.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public class MessageExpirationTimer: ZMMessageTimer, ZMContextChangeTracker { diff --git a/wire-ios-request-strategy/Sources/Helpers/MessageLogAttributesBuilder.swift b/wire-ios-request-strategy/Sources/Helpers/MessageLogAttributesBuilder.swift index 670614cc8d7..29f6cd867ef 100644 --- a/wire-ios-request-strategy/Sources/Helpers/MessageLogAttributesBuilder.swift +++ b/wire-ios-request-strategy/Sources/Helpers/MessageLogAttributesBuilder.swift @@ -18,6 +18,7 @@ import CoreData import WireDataModel +import WireLogging /// Provides log attributes for messages of supported message types. struct MessageLogAttributesBuilder { diff --git a/wire-ios-request-strategy/Sources/Helpers/ZMConversation+Notifications.swift b/wire-ios-request-strategy/Sources/Helpers/ZMConversation+Notifications.swift index 176133d5a2f..a961fe84096 100644 --- a/wire-ios-request-strategy/Sources/Helpers/ZMConversation+Notifications.swift +++ b/wire-ios-request-strategy/Sources/Helpers/ZMConversation+Notifications.swift @@ -20,9 +20,6 @@ import Foundation @objc public extension ZMConversation { - static let failedToDecryptMessageNotificationName = Notification - .Name(rawValue: "ZMConversationFailedToDecryptMessageNotificationName") - static let failedToSendMessageNotificationName = Notification .Name(rawValue: "ZMConversationFailedToSendMessageNotificationName") diff --git a/wire-ios-request-strategy/Sources/Message Sending/MessageDependencyResolver.swift b/wire-ios-request-strategy/Sources/Message Sending/MessageDependencyResolver.swift index b0011485cf1..013371154ef 100644 --- a/wire-ios-request-strategy/Sources/Message Sending/MessageDependencyResolver.swift +++ b/wire-ios-request-strategy/Sources/Message Sending/MessageDependencyResolver.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging // sourcery: AutoMockable public protocol MessageDependencyResolverInterface { diff --git a/wire-ios-request-strategy/Sources/Message Sending/MessageInfoExtractor.swift b/wire-ios-request-strategy/Sources/Message Sending/MessageInfoExtractor.swift index b0392bd2f8c..dd2e03bc562 100644 --- a/wire-ios-request-strategy/Sources/Message Sending/MessageInfoExtractor.swift +++ b/wire-ios-request-strategy/Sources/Message Sending/MessageInfoExtractor.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging enum MessageInfoExtractorError: Error { case missingConversation diff --git a/wire-ios-request-strategy/Sources/Message Sending/MessageSender.swift b/wire-ios-request-strategy/Sources/Message Sending/MessageSender.swift index 97fe0afab37..8b21b77e052 100644 --- a/wire-ios-request-strategy/Sources/Message Sending/MessageSender.swift +++ b/wire-ios-request-strategy/Sources/Message Sending/MessageSender.swift @@ -17,6 +17,7 @@ // import WireDataModel +import WireLogging public enum MessageSendError: Error, Equatable { case missingMessageProtocol diff --git a/wire-ios-request-strategy/Sources/Notifications/NotificationStreamSync.swift b/wire-ios-request-strategy/Sources/Notifications/NotificationStreamSync.swift index 16b409a3465..bee5a2740fb 100644 --- a/wire-ios-request-strategy/Sources/Notifications/NotificationStreamSync.swift +++ b/wire-ios-request-strategy/Sources/Notifications/NotificationStreamSync.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public protocol NotificationStreamSyncDelegate: AnyObject { func fetchedEvents(_ events: [ZMUpdateEvent], hasMoreToFetch: Bool) diff --git a/wire-ios-request-strategy/Sources/Notifications/Push Notifications/Notification Types/Content/ZMLocalNotification.swift b/wire-ios-request-strategy/Sources/Notifications/Push Notifications/Notification Types/Content/ZMLocalNotification.swift index 82a00942b21..044e0636541 100644 --- a/wire-ios-request-strategy/Sources/Notifications/Push Notifications/Notification Types/Content/ZMLocalNotification.swift +++ b/wire-ios-request-strategy/Sources/Notifications/Push Notifications/Notification Types/Content/ZMLocalNotification.swift @@ -18,6 +18,7 @@ import UserNotifications import WireDataModel +import WireLogging /// Defines the various types of local notifications, some of which /// have associated subtypes. diff --git a/wire-ios-request-strategy/Sources/Notifications/PushNotificationStatus.swift b/wire-ios-request-strategy/Sources/Notifications/PushNotificationStatus.swift index c82615f1f0f..687dae1e7d6 100644 --- a/wire-ios-request-strategy/Sources/Notifications/PushNotificationStatus.swift +++ b/wire-ios-request-strategy/Sources/Notifications/PushNotificationStatus.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging private let zmLog = ZMSLog(tag: "PushNotificationStatus") diff --git a/wire-ios-request-strategy/Sources/Payloads/Conversation/Payload+Conversation.swift b/wire-ios-request-strategy/Sources/Payloads/Conversation/Payload+Conversation.swift index 19b5995199e..8b81db60b68 100644 --- a/wire-ios-request-strategy/Sources/Payloads/Conversation/Payload+Conversation.swift +++ b/wire-ios-request-strategy/Sources/Payloads/Conversation/Payload+Conversation.swift @@ -131,7 +131,7 @@ extension Payload { case .v0, .v1, .v2: self.legacyAccessRole = try container.decodeIfPresent(String.self, forKey: .accessRole) self.accessRoles = try container.decodeIfPresent([String].self, forKey: .accessRoleV2) - case .v3, .v4, .v5, .v6: + case .v3, .v4, .v5, .v6, .v7: // v3 replaces the field "access_role_v2" with "access_role". // However, since the format of update events does not depend on versioning, @@ -151,7 +151,7 @@ extension Payload { case .v0, .v1, .v2, .v3, .v4: self.cipherSuite = nil self.epochTimestamp = nil - case .v5, .v6: + case .v5, .v6, .v7: self.cipherSuite = try container.decodeIfPresent(UInt16.self, forKey: .cipherSuite) self.epochTimestamp = try container.decodeIfPresent(Date.self, forKey: .epochTimestamp) } @@ -180,7 +180,7 @@ extension Payload { case .v0, .v1, .v2: try container.encodeIfPresent(legacyAccessRole, forKey: .accessRole) try container.encodeIfPresent(accessRoles, forKey: .accessRoleV2) - case .v3, .v4, .v5, .v6: + case .v3, .v4, .v5, .v6, .v7: if legacyAccessRole == nil { try container.encodeIfPresent(accessRoles, forKey: .accessRole) } else { diff --git a/wire-ios-request-strategy/Sources/Payloads/Conversation/Payload+NewConversation.swift b/wire-ios-request-strategy/Sources/Payloads/Conversation/Payload+NewConversation.swift index 386f4e5a3d4..0268c8bcfca 100644 --- a/wire-ios-request-strategy/Sources/Payloads/Conversation/Payload+NewConversation.swift +++ b/wire-ios-request-strategy/Sources/Payloads/Conversation/Payload+NewConversation.swift @@ -121,7 +121,7 @@ extension Payload { case .v0, .v1, .v2: self.legacyAccessRole = try container.decodeIfPresent(String.self, forKey: .accessRole) self.accessRoles = try container.decodeIfPresent([String].self, forKey: .accessRoleV2) - case .v3, .v4, .v5, .v6: + case .v3, .v4, .v5, .v6, .v7: self.accessRoles = try container.decodeIfPresent([String].self, forKey: .accessRole) self.legacyAccessRole = nil } @@ -145,7 +145,7 @@ extension Payload { case .v0, .v1, .v2: try container.encodeIfPresent(legacyAccessRole, forKey: .accessRole) try container.encodeIfPresent(accessRoles, forKey: .accessRoleV2) - case .v3, .v4, .v5, .v6: + case .v3, .v4, .v5, .v6, .v7: try container.encodeIfPresent(accessRoles, forKey: .accessRole) } } diff --git a/wire-ios-request-strategy/Sources/Payloads/Payload+Coding.swift b/wire-ios-request-strategy/Sources/Payloads/Payload+Coding.swift index 9a471f672e3..445ad6d3273 100644 --- a/wire-ios-request-strategy/Sources/Payloads/Payload+Coding.swift +++ b/wire-ios-request-strategy/Sources/Payloads/Payload+Coding.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging // MARK: JSON Decoder / Encoder diff --git a/wire-ios-request-strategy/Sources/Payloads/Processing/ConnectionPayloadProcessor.swift b/wire-ios-request-strategy/Sources/Payloads/Processing/ConnectionPayloadProcessor.swift index 27758ada22e..eaf2b5e968b 100644 --- a/wire-ios-request-strategy/Sources/Payloads/Processing/ConnectionPayloadProcessor.swift +++ b/wire-ios-request-strategy/Sources/Payloads/Processing/ConnectionPayloadProcessor.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging final class ConnectionPayloadProcessor { diff --git a/wire-ios-request-strategy/Sources/Payloads/Processing/ConversationEventPayloadProcessor.swift b/wire-ios-request-strategy/Sources/Payloads/Processing/ConversationEventPayloadProcessor.swift index 50eda5243f0..1ce04418c74 100644 --- a/wire-ios-request-strategy/Sources/Payloads/Processing/ConversationEventPayloadProcessor.swift +++ b/wire-ios-request-strategy/Sources/Payloads/Processing/ConversationEventPayloadProcessor.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging enum ConversationEventPayloadProcessorError: Error { case noBackendConversationId @@ -177,7 +178,8 @@ struct ConversationEventPayloadProcessor { return (isSelfUserRemoved, conversation.messageProtocol) } - if DeveloperFlag.enableMLSSupport.isOn { + let mlsFeature = await FeatureRepository(context: context).fetchMLS() + if mlsFeature.isEnabled { if isSelfUserRemoved, messageProtocol.isOne(of: .mls, .mixed) { await mlsEventProcessor.wipeMLSGroup(forConversation: conversation, context: context) } @@ -926,7 +928,9 @@ struct ConversationEventPayloadProcessor { context: NSManagedObjectContext, source: Source ) async { - guard DeveloperFlag.enableMLSSupport.isOn else { return } + let mlsFeature = await FeatureRepository(context: context).fetchMLS() + guard mlsFeature.isEnabled else { return } + await mlsEventProcessor.updateConversationIfNeeded( conversation: conversation, fallbackGroupID: payload.mlsGroupID.map { .init(base64Encoded: $0) } ?? nil, diff --git a/wire-ios-request-strategy/Sources/Payloads/Processing/Helpers/MLSEventProcessor.swift b/wire-ios-request-strategy/Sources/Payloads/Processing/Helpers/MLSEventProcessor.swift index 213cec1d315..af53c510b3e 100644 --- a/wire-ios-request-strategy/Sources/Payloads/Processing/Helpers/MLSEventProcessor.swift +++ b/wire-ios-request-strategy/Sources/Payloads/Processing/Helpers/MLSEventProcessor.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging // sourcery: AutoMockable public protocol MLSEventProcessing { @@ -174,13 +175,14 @@ public class MLSEventProcessor: MLSEventProcessing { return logWarn(aborting: .processingWelcome, withReason: .missingMLSService) } let migrator = OneOnOneMigrator(mlsService: mlsService) + let mlsFeature = await FeatureRepository(context: context).fetchMLS() await process( welcomeMessage: welcomeMessage, conversationID: conversationID, in: context, mlsService: mlsService, - oneOnOneResolver: OneOnOneResolver(migrator: migrator) + oneOnOneResolver: OneOnOneResolver(migrator: migrator, isMLSEnabled: mlsFeature.isEnabled) ) } diff --git a/wire-ios-request-strategy/Sources/Payloads/Processing/MessageSendingStatusPayloadProcessor.swift b/wire-ios-request-strategy/Sources/Payloads/Processing/MessageSendingStatusPayloadProcessor.swift index 8f201f74550..d347bb93508 100644 --- a/wire-ios-request-strategy/Sources/Payloads/Processing/MessageSendingStatusPayloadProcessor.swift +++ b/wire-ios-request-strategy/Sources/Payloads/Processing/MessageSendingStatusPayloadProcessor.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging final class MessageSendingStatusPayloadProcessor { diff --git a/wire-ios-request-strategy/Sources/Protocols/OTREntity.swift b/wire-ios-request-strategy/Sources/Protocols/OTREntity.swift index 255b448f405..cc9c2095a37 100644 --- a/wire-ios-request-strategy/Sources/Protocols/OTREntity.swift +++ b/wire-ios-request-strategy/Sources/Protocols/OTREntity.swift @@ -167,7 +167,7 @@ extension OTREntity { context: context ) - case .v4, .v5, .v6: + case .v4, .v5, .v6, .v7: guard let payload = Payload.MessageSendingStatusV4(response) else { return (missingClients: Set(), deletedClients: Set()) } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetClientMessageRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetClientMessageRequestStrategy.swift index 21f7b69ee6c..a11f904662f 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetClientMessageRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetClientMessageRequestStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging /// The `AssetClientMessageRequestStrategy` for creating requests to insert the genericMessage of a /// `ZMAssetClientMessage` remotely. This is only necessary for the `/assets/v3' endpoint as we diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategy.swift index 955e5ff6236..ab5ed88e57d 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/AssetV3UploadRequestStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging /// AssetV3UploadRequestStrategy is responsible for uploading all the assets associated with a asset message /// after they've been preprocessed (downscaled & encrypted). After all the assets have been uploaded diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Helpers/AssetDownloadRequestFactory.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Helpers/AssetDownloadRequestFactory.swift index 413adef71da..310fd7fe391 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Helpers/AssetDownloadRequestFactory.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Helpers/AssetDownloadRequestFactory.swift @@ -36,7 +36,7 @@ public final class AssetDownloadRequestFactory { case .v1: guard let domain else { return nil } path = "/assets/v4/\(domain)/\(key)" - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: guard let domain else { return nil } path = "/assets/\(domain)/\(key)" } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetDownloadRequestStrategyTests.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetDownloadRequestStrategyTests.swift index b363ea66ec8..93a358778da 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetDownloadRequestStrategyTests.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetDownloadRequestStrategyTests.swift @@ -143,7 +143,7 @@ extension LinkPreviewAssetDownloadRequestStrategyTests { guard let request = self.sut.nextRequest(for: self.apiVersion) else { XCTFail("No request generated"); return } XCTAssertEqual(request.path, "/assets/v3/\(assetID)") - XCTAssertEqual(request.method, ZMTransportRequestMethod.get) + XCTAssertEqual(request.method, .get) XCTAssertNil(self.sut.nextRequest(for: self.apiVersion)) } } @@ -169,7 +169,7 @@ extension LinkPreviewAssetDownloadRequestStrategyTests { guard let request = self.sut.nextRequest(for: self.apiVersion) else { XCTFail("No request generated"); return } XCTAssertEqual(request.path, "/assets/v3/\(assetID)") - XCTAssertEqual(request.method, ZMTransportRequestMethod.get) + XCTAssertEqual(request.method, .get) XCTAssertNil(self.sut.nextRequest(for: self.apiVersion)) } } @@ -197,7 +197,7 @@ extension LinkPreviewAssetDownloadRequestStrategyTests { guard let request = self.sut.nextRequest(for: self.apiVersion) else { XCTFail("No request generated"); return } XCTAssertEqual(request.path, "/v1/assets/v4/\(assetDomain)/\(assetID)") - XCTAssertEqual(request.method, ZMTransportRequestMethod.get) + XCTAssertEqual(request.method, .get) XCTAssertNil(self.sut.nextRequest(for: self.apiVersion)) } } @@ -225,7 +225,7 @@ extension LinkPreviewAssetDownloadRequestStrategyTests { guard let request = self.sut.nextRequest(for: self.apiVersion) else { XCTFail("No request generated"); return } XCTAssertEqual(request.path, "/v1/assets/v4/\(assetDomain)/\(assetID)") - XCTAssertEqual(request.method, ZMTransportRequestMethod.get) + XCTAssertEqual(request.method, .get) XCTAssertNil(self.sut.nextRequest(for: self.apiVersion)) } } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategyTests.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategyTests.swift index 18fe0f7271b..e582ba126d8 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategyTests.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewAssetUploadRequestStrategyTests.swift @@ -183,7 +183,7 @@ extension LinkPreviewAssetUploadRequestStrategyTests { // THEN XCTAssertNotNil(request) XCTAssertEqual(request?.path, "/assets/v3") - XCTAssertEqual(request?.method, ZMTransportRequestMethod.post) + XCTAssertEqual(request?.method, .post) } } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewUpdateRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewUpdateRequestStrategy.swift index 47f38fe2193..94df0132180 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewUpdateRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/Link Preview/LinkPreviewUpdateRequestStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public class LinkPreviewUpdateRequestStrategy: NSObject, ZMContextChangeTrackerSource { diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/V2/AssetV2DownloadRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/V2/AssetV2DownloadRequestStrategy.swift index f23f7a48f10..fc3733d3835 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/V2/AssetV2DownloadRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/V2/AssetV2DownloadRequestStrategy.swift @@ -193,7 +193,7 @@ public final class AssetV2DownloadRequestStrategy: AbstractRequestStrategy, ZMDo fatalError("Cannot generate request for \(object.safeForLoggingDescription)") - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: return nil } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Assets/V2/ImageV2DownloadRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/Assets/V2/ImageV2DownloadRequestStrategy.swift index 8b643e57494..3ab9bc99c8e 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Assets/V2/ImageV2DownloadRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Assets/V2/ImageV2DownloadRequestStrategy.swift @@ -117,7 +117,7 @@ extension ImageV2DownloadRequestStrategy: ZMDownstreamTranscoder { ) } - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: // v2 assets are legacy and no longer supported in API v2 return nil } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Availability/AvailabilityRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/Availability/AvailabilityRequestStrategy.swift index c36d0ba2cf0..3d74b332d10 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Availability/AvailabilityRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Availability/AvailabilityRequestStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public class AvailabilityRequestStrategy: NSObject, ZMContextChangeTrackerSource { diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Client Message/ClientMessageRequestFactory.swift b/wire-ios-request-strategy/Sources/Request Strategies/Client Message/ClientMessageRequestFactory.swift index f7cf3c9e51d..79f8101da8b 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Client Message/ClientMessageRequestFactory.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Client Message/ClientMessageRequestFactory.swift @@ -56,7 +56,7 @@ public final class ClientMessageRequestFactory: NSObject { missingClientsStrategy: .doNotIgnoreAnyMissingClient ) - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: let domain = if let domain, !domain.isEmpty { domain } else { BackendInfo.domain } guard let domain else { zmLog.error("could not create request: missing domain") diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Client Message/ClientMessageRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/Client Message/ClientMessageRequestStrategy.swift index f537ebad982..a0390244fa8 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Client Message/ClientMessageRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Client Message/ClientMessageRequestStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public class ClientMessageRequestStrategy: NSObject, ZMContextChangeTrackerSource { diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Connection/Actions/ConnectToUserActionHandler.swift b/wire-ios-request-strategy/Sources/Request Strategies/Connection/Actions/ConnectToUserActionHandler.swift index 077e15e56a6..b3515c46466 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Connection/Actions/ConnectToUserActionHandler.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Connection/Actions/ConnectToUserActionHandler.swift @@ -32,7 +32,7 @@ class ConnectToUserActionHandler: ActionHandler { switch apiVersion { case .v0: nonFederatedRequest(for: action, apiVersion: apiVersion) - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: federatedRequest(for: action, apiVersion: apiVersion) } } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Connection/Actions/UpdateConnectionActionHandler.swift b/wire-ios-request-strategy/Sources/Request Strategies/Connection/Actions/UpdateConnectionActionHandler.swift index 2e5f0e05893..e6ea2464199 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Connection/Actions/UpdateConnectionActionHandler.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Connection/Actions/UpdateConnectionActionHandler.swift @@ -30,7 +30,7 @@ class UpdateConnectionActionHandler: ActionHandler { case .v0: v0Request(for: action) - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: v1Request(for: action) } } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Connection/ConnectionRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/Connection/ConnectionRequestStrategy.swift index 66c9a50a8c6..056aa59116b 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Connection/ConnectionRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Connection/ConnectionRequestStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public class ConnectionRequestStrategy: AbstractRequestStrategy, ZMRequestGeneratorSource, ZMContextChangeTrackerSource { @@ -123,7 +124,7 @@ public class ConnectionRequestStrategy: AbstractRequestStrategy, ZMRequestGenera } } - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: connectionListSync.fetch { [weak self] result in switch result { case let .success(connectionList): @@ -194,7 +195,7 @@ extension ConnectionRequestStrategy: KeyPathObjectSyncTranscoder { connectionByIDSync.sync(identifiers: userIdSet) } - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: if let qualifiedID = object.to.qualifiedID { let qualifiedIdSet: Set = [qualifiedID] connectionByQualifiedIDSync.sync(identifiers: qualifiedIdSet) diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/AddParticipantActionHandler.swift b/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/AddParticipantActionHandler.swift index b250c20cb85..28ea155bd3a 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/AddParticipantActionHandler.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/AddParticipantActionHandler.swift @@ -62,7 +62,7 @@ class AddParticipantActionHandler: ActionHandler { v0Request(for: action) case .v1: v1Request(for: action) - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: v2Request(for: action, apiVersion: apiVersion) } } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/CreateConversationGuestLinkActionHandler.swift b/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/CreateConversationGuestLinkActionHandler.swift index 70a845ca62e..0e8325c0178 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/CreateConversationGuestLinkActionHandler.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/CreateConversationGuestLinkActionHandler.swift @@ -38,7 +38,7 @@ final class CreateConversationGuestLinkActionHandler: ActionHandler { switch apiVersion { case .v0: nonFederatedRequest(for: action, apiVersion: apiVersion) - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: federatedRequest(for: action, apiVersion: apiVersion) } } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/SetAllowGuestsAndServicesActionHandler.swift b/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/SetAllowGuestsAndServicesActionHandler.swift index 471559026ee..899cd9e87af 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/SetAllowGuestsAndServicesActionHandler.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/SetAllowGuestsAndServicesActionHandler.swift @@ -60,7 +60,7 @@ final class SetAllowGuestsAndServicesActionHandler: ActionHandler apiVersion: apiVersion.rawValue ) - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: return ZMTransportRequest( path: "/conversations/list", method: .post, diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/SyncUsersActionHandler.swift b/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/SyncUsersActionHandler.swift index 5c5684d95e4..f3eec766950 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/SyncUsersActionHandler.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/SyncUsersActionHandler.swift @@ -60,7 +60,7 @@ class SyncUsersActionHandler: ActionHandler { action.fail(with: .endpointUnavailable) return nil - case .v4, .v5, .v6: + case .v4, .v5, .v6, .v7: guard let payloadData = RequestPayload(qualified_ids: action.qualifiedIDs).payloadString() else { @@ -95,7 +95,7 @@ class SyncUsersActionHandler: ActionHandler { action.fail(with: .endpointUnavailable) return - case .v4, .v5, .v6: + case .v4, .v5, .v6, .v7: switch response.httpStatus { case 200: guard let rawData = response.rawData, diff --git a/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/UpdateAccessRolesActionHandler.swift b/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/UpdateAccessRolesActionHandler.swift index 846200b47be..98934b67e2e 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/UpdateAccessRolesActionHandler.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/Conversation/Actions/UpdateAccessRolesActionHandler.swift @@ -60,7 +60,7 @@ final class UpdateAccessRolesActionHandler: ActionHandler= .v7 + ? "/one2one-conversations/\(domain)/\(userID)" + : "/conversations/one2one/\(domain)/\(userID)" return ZMTransportRequest( - getFromPath: "/conversations/one2one/\(domain)/\(userID)", + getFromPath: path, apiVersion: apiVersion.rawValue ) } @@ -89,7 +92,7 @@ final class SyncMLSOneToOneConversationActionHandler: ActionHandler? + + // Action + + private let actionHandler: FetchBackendMLSPublicKeysActionHandler + private let actionSync: EntityActionSync + + // MARK: - Life cycle + + public init( + withManagedObjectContext managedObjectContext: NSManagedObjectContext, + applicationStatus: ApplicationStatus, + syncProgress: SyncProgress + ) { + self.actionHandler = FetchBackendMLSPublicKeysActionHandler(context: managedObjectContext) + self.actionSync = EntityActionSync(actionHandlers: [actionHandler]) + self.syncStatus = syncProgress + + super.init( + withManagedObjectContext: managedObjectContext, + applicationStatus: applicationStatus + ) + + configuration = [ + .allowsRequestsWhileUnauthenticated, + .allowsRequestsWhileOnline, + .allowsRequestsDuringQuickSync, + .allowsRequestsDuringSlowSync, + .allowsRequestsWhileWaitingForWebsocket, + .allowsRequestsWhileInBackground + ] + } + + deinit { + slowSyncTask?.cancel() + } + + // MARK: - Request + + public override func nextRequestIfAllowed(for apiVersion: APIVersion) -> ZMTransportRequest? { + if isSlowSyncing, slowSyncTask == nil { + slowSyncTask = Task { [weak self, syncStatus, syncPhase] in + guard let self, !Task.isCancelled else { return } + + WireLogger.mls.info("slow sync start fetch backend MLS public keys!") + + do { + // perform action notifies the registered action handler `FetchBackendMLSPublicKeysActionHandler`. + // the action stay pending until in the operation loop creates and executes the next request. + // Here the task waits for the result and then continues to report to syncStatus. + + var action = FetchBackendMLSPublicKeysAction() + let backendPublicKeys = try await action.perform(in: managedObjectContext.notificationContext) + let hasValidKeys = backendPublicKeys.removal.hasValidKeys() + BackendInfo.isMLSEnabled = hasValidKeys + + WireLogger.mls.info("slow sync finished fetch backend MLS public keys!") + } catch { + // If we get an error while fetching MLS public keys, + // it shouldn't fail the current phase. This is expected behavior for some customers. + // More details here: https://wearezeta.atlassian.net/browse/WPB-14455 + BackendInfo.isMLSEnabled = false + + WireLogger.mls.info("slow sync can't fetch backend MLS public keys!") + } + await managedObjectContext.perform { + syncStatus.finishCurrentSyncPhase(phase: syncPhase) + } + + slowSyncTask = nil + } + } + + return actionSync.nextRequest(for: apiVersion) + } +} diff --git a/wire-ios-request-strategy/Sources/Request Strategies/MLS/MLSRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/MLS/MLSRequestStrategy.swift index 048319fdbcd..f17eccc1bf7 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/MLS/MLSRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/MLS/MLSRequestStrategy.swift @@ -40,7 +40,6 @@ public final class MLSRequestStrategy: AbstractRequestStrategy { CountSelfMLSKeyPackagesActionHandler(context: managedObjectContext), UploadSelfMLSKeyPackagesActionHandler(context: managedObjectContext), ClaimMLSKeyPackageActionHandler(context: managedObjectContext), - FetchBackendMLSPublicKeysActionHandler(context: managedObjectContext), FetchMLSSubconversationGroupInfoActionHandler(context: managedObjectContext), FetchMLSConversationGroupInfoActionHandler(context: managedObjectContext), FetchSubgroupActionHandler(context: managedObjectContext), diff --git a/wire-ios-request-strategy/Sources/Request Strategies/User Clients/FetchUserClientsActionHandler.swift b/wire-ios-request-strategy/Sources/Request Strategies/User Clients/FetchUserClientsActionHandler.swift index deb2caaa7c9..2dfc9ca1bba 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/User Clients/FetchUserClientsActionHandler.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/User Clients/FetchUserClientsActionHandler.swift @@ -40,7 +40,7 @@ final class FetchUserClientsActionHandler: ActionHandler action.fail(with: .endpointUnavailable) return nil - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: guard let payloadData = RequestPayload(qualified_users: action.userIDs).payloadData(), let payloadString = String(bytes: payloadData, encoding: .utf8) @@ -80,7 +80,7 @@ final class FetchUserClientsActionHandler: ActionHandler case .v0: return - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: switch response.httpStatus { case 200: guard let rawData = response.rawData else { diff --git a/wire-ios-request-strategy/Sources/Request Strategies/User Clients/FetchingClientRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/User Clients/FetchingClientRequestStrategy.swift index 4e970c14df1..96be09d699c 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/User Clients/FetchingClientRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/User Clients/FetchingClientRequestStrategy.swift @@ -122,7 +122,7 @@ public final class FetchingClientRequestStrategy: AbstractRequestStrategy { self.userClientsByUserID.sync(identifiers: userIdSet) } - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: let domain = if let domain = user.domain, !domain.isEmpty { domain } else { BackendInfo.domain } if let domain { let qualifiedID = QualifiedID(uuid: userID, domain: domain) @@ -194,7 +194,7 @@ extension FetchingClientRequestStrategy: ZMContextChangeTracker, ZMContextChange result.1.append(userClientID) } - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: if let qualifiedID = qualifiedIDWithFallback(from: userClient) { result.0.append(qualifiedID) } @@ -344,7 +344,7 @@ final class UserClientByQualifiedUserIDTranscoder: IdentifierObjectSyncTranscode case .v1: return v1Request(for: identifiers) - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: return v2Request(for: identifiers, apiVersion: apiVersion) } } @@ -404,7 +404,7 @@ final class UserClientByQualifiedUserIDTranscoder: IdentifierObjectSyncTranscode completionHandler() return - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: WaitingGroupTask(context: managedObjectContext) { [self] in await commonResponseHandling(response: response, for: identifiers) completionHandler() diff --git a/wire-ios-request-strategy/Sources/Request Strategies/User Clients/ResetSessionRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/User Clients/ResetSessionRequestStrategy.swift index 0918cc128dc..1bc04afa169 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/User Clients/ResetSessionRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/User Clients/ResetSessionRequestStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public class ResetSessionRequestStrategy: NSObject, ZMContextChangeTrackerSource { diff --git a/wire-ios-request-strategy/Sources/Request Strategies/User Clients/VerifyLegalHoldRequestStrategyTests.swift b/wire-ios-request-strategy/Sources/Request Strategies/User Clients/VerifyLegalHoldRequestStrategyTests.swift index 2469c17612f..9825fa5b6b2 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/User Clients/VerifyLegalHoldRequestStrategyTests.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/User Clients/VerifyLegalHoldRequestStrategyTests.swift @@ -116,7 +116,7 @@ class VerifyLegalHoldRequestStrategyTests: MessagingTestBase { var expectedPath = switch apiVersion { case .v0: "/conversations/\(conversation.remoteIdentifier!.transportString())/otr/messages" - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: "/v\(apiVersion.rawValue)/conversations/\(conversation.domain!)/\(conversation.remoteIdentifier!.transportString())/proteus/messages" } @@ -176,7 +176,7 @@ class VerifyLegalHoldRequestStrategyTests: MessagingTestBase { ClientUpdateResponse(missing: clientListByUserID).transportData case .v1, .v2, .v3: Payload.MessageSendingStatusV1(missing: [self.otherUser.domain!: clientListByUserID]).transportData - case .v4, .v5, .v6: + case .v4, .v5, .v6, .v7: Payload.MessageSendingStatusV4(missing: [self.otherUser.domain!: clientListByUserID]).transportData } @@ -237,7 +237,7 @@ class VerifyLegalHoldRequestStrategyTests: MessagingTestBase { ClientUpdateResponse(missing: clientListByUserID).transportData case .v1, .v2, .v3: Payload.MessageSendingStatusV1(missing: [self.otherUser.domain!: clientListByUserID]).transportData - case .v4, .v5, .v6: + case .v4, .v5, .v6, .v7: Payload.MessageSendingStatusV4(missing: [self.otherUser.domain!: clientListByUserID]).transportData } @@ -296,7 +296,7 @@ class VerifyLegalHoldRequestStrategyTests: MessagingTestBase { ClientUpdateResponse(missing: ClientListByUser()).transportData case .v1, .v2, .v3: Payload.MessageSendingStatusV1(missing: UserListByDomain()).transportData - case .v4, .v5, .v6: + case .v4, .v5, .v6, .v7: Payload.MessageSendingStatusV4(missing: UserListByDomain()).transportData } @@ -346,7 +346,7 @@ class VerifyLegalHoldRequestStrategyTests: MessagingTestBase { ClientUpdateResponse(missing: clientListByUserID).transportData case .v1, .v2, .v3: Payload.MessageSendingStatusV1(missing: [selfUser.domain!: clientListByUserID]).transportData - case .v4, .v5, .v6: + case .v4, .v5, .v6, .v7: Payload.MessageSendingStatusV4(missing: [selfUser.domain!: clientListByUserID]).transportData } diff --git a/wire-ios-request-strategy/Sources/Request Strategies/User/UserProfileRequestStrategy.swift b/wire-ios-request-strategy/Sources/Request Strategies/User/UserProfileRequestStrategy.swift index 946b4af0a66..5a28e6341cd 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/User/UserProfileRequestStrategy.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/User/UserProfileRequestStrategy.swift @@ -15,7 +15,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. // + import Foundation +import WireLogging /// Request strategy for fetching user profiles and processing user update events. /// @@ -106,7 +108,7 @@ public class UserProfileRequestStrategy: AbstractRequestStrategy, IdentifierObje case .v0: userProfileByID.sync(identifiers: users.compactMap(\.remoteIdentifier)) - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: if let qualifiedUserIDs = users.qualifiedUserIDs { userProfileByQualifiedID.sync(identifiers: qualifiedUserIDs) } else if let domain = BackendInfo.domain { @@ -372,7 +374,7 @@ class UserProfileByQualifiedIDTranscoder: IdentifierObjectSyncTranscoder { let missingIdentifiers = identifiers.subtracting(payload.compactMap(\.qualifiedID)) markUserProfilesAsFetched(missingIdentifiers) - case .v4, .v5, .v6: + case .v4, .v5, .v6, .v7: guard let rawData = response.rawData, let payload = Payload.UserProfilesV4(rawData, decoder: decoder) diff --git a/wire-ios-request-strategy/Sources/Request Strategies/User/UserProfileRequestStrategyTests.swift b/wire-ios-request-strategy/Sources/Request Strategies/User/UserProfileRequestStrategyTests.swift index 64799d59e49..065f2af24b2 100644 --- a/wire-ios-request-strategy/Sources/Request Strategies/User/UserProfileRequestStrategyTests.swift +++ b/wire-ios-request-strategy/Sources/Request Strategies/User/UserProfileRequestStrategyTests.swift @@ -546,7 +546,7 @@ class UserProfileRequestStrategyTests: MessagingTestBase { switch apiVersion { case .v0, .v1, .v2, .v3: payloadData = userProfiles.payloadData() - case .v4, .v5, .v6: + case .v4, .v5, .v6, .v7: let userProfiles = Payload.UserProfilesV4(found: userProfiles, failed: failed) payloadData = userProfiles.payloadData() } diff --git a/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder+MLS.swift b/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder+MLS.swift index c269b1a54df..e398f5f7d5c 100644 --- a/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder+MLS.swift +++ b/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder+MLS.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging extension EventDecoder { diff --git a/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder+Proteus.swift b/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder+Proteus.swift index 0eaab567061..1461b6f0359 100644 --- a/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder+Proteus.swift +++ b/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder+Proteus.swift @@ -17,7 +17,9 @@ // import Foundation +import WireCoreCrypto import WireCryptobox +import WireLogging private let zmLog = ZMSLog(tag: "EventDecoder") @@ -113,15 +115,6 @@ extension EventDecoder { (createdNewSession, decryptedEvent) = result - } catch let error as CBoxResult { - let proteusError = ProteusError(cboxResult: error) - fail(error: proteusError) - WireLogger.updateEvent.error( - "decrypting proteus event... failed with proteus error: \(proteusError?.localizedDescription ?? "?")", - attributes: event.logAttributes - ) - return nil - } catch let error as ProteusService.DecryptionError { let proteusError = error.proteusError fail(error: proteusError) @@ -196,39 +189,31 @@ extension EventDecoder { ) WireLogger.updateEvent.debug("event debug: \(event.debugInformation)") - if error == .outdatedMessage || error == .duplicateMessage { + if error == .DuplicateMessage { // Do not notify the user if the error is just "duplicated". return } - var conversation: ZMConversation? - if let conversationID = event.conversationUUID { - conversation = ZMConversation.fetch( + // Append system message. + guard + let error, + let senderUser = sender.user, + let conversationID = event.conversationUUID, + let conversation = ZMConversation.fetch( with: conversationID, domain: event.conversationDomain, in: context ) - if let senderUser = sender.user { - conversation?.appendDecryptionFailedSystemMessage( - at: event.timestamp, - sender: senderUser, - client: sender, - errorCode: Int(error?.rawValue ?? 0) - ) - } + else { + return } - let userInfo: [String: Any] = [ - "cause": error?.rawValue as Any, - "deviceClass": sender.deviceClass ?? "" - ] - - NotificationInContext( - name: ZMConversation.failedToDecryptMessageNotificationName, - context: context.notificationContext, - object: conversation, - userInfo: userInfo - ).post() + conversation.appendDecryptionFailedSystemMessage( + at: event.timestamp, + sender: senderUser, + client: sender, + error: error + ) } // Returns the decrypted version of an update event. This is generated by decrypting @@ -258,7 +243,7 @@ extension EventDecoder { using decryptFunction: ProteusDecryptionFunction ) async throws -> (didCreateNewSession: Bool, decryptedData: Data)? { guard - let encryptedData = try event.encryptedMessageData() + let encryptedData = event.encryptedMessageData() else { return nil } @@ -291,7 +276,7 @@ private extension ZMUpdateEvent { return eventData } - func encryptedMessageData() throws -> Data? { + func encryptedMessageData() -> Data? { guard let key = payloadKey, let string = eventData?[key] as? String, @@ -300,16 +285,6 @@ private extension ZMUpdateEvent { return nil } - // We need to check the size of the encrypted data payload for regular OTR and external messages. - let maxReceivingSize = Int(12_000 * 1.5) - - guard - string.count <= maxReceivingSize, - externalStringCount <= maxReceivingSize - else { - throw CBOX_DECODE_ERROR - } - return data } diff --git a/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder.swift b/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder.swift index 3ede2d316f4..8784bd321d5 100644 --- a/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder.swift +++ b/wire-ios-request-strategy/Sources/Synchronization/Decoding/EventDecoder.swift @@ -19,6 +19,7 @@ import Foundation import WireCryptobox import WireDataModel +import WireLogging import WireUtilities private let zmLog = ZMSLog(tag: "EventDecoder") diff --git a/wire-ios-request-strategy/Sources/Synchronization/Decoding/StoreUpdateEvent.swift b/wire-ios-request-strategy/Sources/Synchronization/Decoding/StoreUpdateEvent.swift index a15cbf08f61..3cb600b85bf 100644 --- a/wire-ios-request-strategy/Sources/Synchronization/Decoding/StoreUpdateEvent.swift +++ b/wire-ios-request-strategy/Sources/Synchronization/Decoding/StoreUpdateEvent.swift @@ -18,6 +18,7 @@ import CoreData import Foundation +import WireLogging @objc(StoredUpdateEvent) public final class StoredUpdateEvent: NSManagedObject { diff --git a/wire-ios-request-strategy/Sources/Synchronization/QuickSyncObserver.swift b/wire-ios-request-strategy/Sources/Synchronization/QuickSyncObserver.swift index 69724922f32..07fbf481167 100644 --- a/wire-ios-request-strategy/Sources/Synchronization/QuickSyncObserver.swift +++ b/wire-ios-request-strategy/Sources/Synchronization/QuickSyncObserver.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging // sourcery: AutoMockable public protocol QuickSyncObserverInterface { diff --git a/wire-ios-request-strategy/Sources/Synchronization/SyncPhase.swift b/wire-ios-request-strategy/Sources/Synchronization/SyncPhase.swift index 8d23cb886b7..33ffe308a96 100644 --- a/wire-ios-request-strategy/Sources/Synchronization/SyncPhase.swift +++ b/wire-ios-request-strategy/Sources/Synchronization/SyncPhase.swift @@ -33,6 +33,7 @@ public enum SyncPhase: Int, CustomStringConvertible, CaseIterable { case fetchingLegalHoldStatus case fetchingLabels case fetchingFeatureConfig + case fetchingBackendMLSPublicKeys case updateSelfSupportedProtocols case evaluate1on1ConversationsForMLS // following is quick sync only @@ -77,6 +78,8 @@ public enum SyncPhase: Int, CustomStringConvertible, CaseIterable { "fetchingLabels" case .fetchingFeatureConfig: "fetchingFeatureConfig" + case .fetchingBackendMLSPublicKeys: + "fetchingBackendMLSPublicKeys" case .updateSelfSupportedProtocols: "updateSelfSupportedProtocols" case .evaluate1on1ConversationsForMLS: diff --git a/wire-ios-request-strategy/Sources/User Client/UserClientRequestFactory.swift b/wire-ios-request-strategy/Sources/User Client/UserClientRequestFactory.swift index ebe37e5c13b..68220b970e3 100644 --- a/wire-ios-request-strategy/Sources/User Client/UserClientRequestFactory.swift +++ b/wire-ios-request-strategy/Sources/User Client/UserClientRequestFactory.swift @@ -33,7 +33,7 @@ public class UserClientRequestFactory { return ZMTransportRequest( path: "/clients/\(clientId)", - method: ZMTransportRequestMethod.delete, + method: .delete, payload: payload as ZMTransportData, apiVersion: apiVersion.rawValue ) diff --git a/wire-ios-request-strategy/Support/Sourcery/generated/AutoMockable.generated.swift b/wire-ios-request-strategy/Support/Sourcery/generated/AutoMockable.generated.swift index a2dbfe7df56..49d1b498a12 100644 --- a/wire-ios-request-strategy/Support/Sourcery/generated/AutoMockable.generated.swift +++ b/wire-ios-request-strategy/Support/Sourcery/generated/AutoMockable.generated.swift @@ -24,15 +24,9 @@ // swiftlint:disable line_length // swiftlint:disable variable_name -public import Foundation -#if os(iOS) || os(tvOS) || os(watchOS) -public import UIKit -#elseif os(OSX) -public import AppKit -#endif - -public import WireCoreCrypto -public import Combine + +import WireCoreCrypto +import Combine @testable import WireRequestStrategy diff --git a/wire-ios-request-strategy/Tests/Helpers/MessagingTestBase.swift b/wire-ios-request-strategy/Tests/Helpers/MessagingTestBase.swift index 665f1a04558..a7076648185 100644 --- a/wire-ios-request-strategy/Tests/Helpers/MessagingTestBase.swift +++ b/wire-ios-request-strategy/Tests/Helpers/MessagingTestBase.swift @@ -18,7 +18,9 @@ import WireCryptobox import WireDataModel +import WireLogging import WireTesting + @testable import WireRequestStrategy class MessagingTestBase: ZMTBaseTest { diff --git a/wire-ios-request-strategy/Tests/Sources/Payloads/Processing/ConversationEventPayloadProcessorTests.swift b/wire-ios-request-strategy/Tests/Sources/Payloads/Processing/ConversationEventPayloadProcessorTests.swift index 9508a1989aa..80598559950 100644 --- a/wire-ios-request-strategy/Tests/Sources/Payloads/Processing/ConversationEventPayloadProcessorTests.swift +++ b/wire-ios-request-strategy/Tests/Sources/Payloads/Processing/ConversationEventPayloadProcessorTests.swift @@ -1102,10 +1102,6 @@ final class ConversationEventPayloadProcessorTests: MessagingTestBase { // MARK: - MLS: Conversation Create func testUpdateOrCreateConversation_Group_MLS_AsksToUpdateConversationIfNeeded() async { - DeveloperFlag.enableMLSSupport.enable(true, storage: .temporary()) - defer { - DeveloperFlag.storage = .standard - } // given let qualifiedID = await syncMOC.perform { self.groupConversation.qualifiedID! @@ -1115,6 +1111,9 @@ final class ConversationEventPayloadProcessorTests: MessagingTestBase { type: BackendConversationType.group.rawValue, messageProtocol: "mls" ) + await syncMOC.perform { + FeatureRepository(context: self.syncMOC).storeMLS(Feature.MLS(status: .enabled)) + } // when await sut.updateOrCreateConversation( @@ -1278,15 +1277,14 @@ final class ConversationEventPayloadProcessorTests: MessagingTestBase { // MARK: - MLS conversation member leave func test_UpdateConversationMemberLeave_WipesMLSGroup() async { - DeveloperFlag.enableMLSSupport.enable(true, storage: .temporary()) - defer { - DeveloperFlag.storage = .standard - } // Given let wipeGroupExpectation = XCTestExpectation(description: "it wipes group") mockMLSEventProcessor.wipeMLSGroupForConversationContext_MockMethod = { _, _ in wipeGroupExpectation.fulfill() } + await syncMOC.perform { + FeatureRepository(context: self.syncMOC).storeMLS(Feature.MLS(status: .enabled)) + } let (payload, updateEvent) = await syncMOC.perform { [self] in // Create self user diff --git a/wire-ios-request-strategy/Tests/TestPlans/AllTests.xctestplan b/wire-ios-request-strategy/Tests/TestPlans/AllTests.xctestplan new file mode 100644 index 00000000000..6eb6d26d5f4 --- /dev/null +++ b/wire-ios-request-strategy/Tests/TestPlans/AllTests.xctestplan @@ -0,0 +1,75 @@ +{ + "configurations" : [ + { + "id" : "1F8A3CDC-E12B-4921-9BD0-0ADB68899DC0", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "commandLineArgumentEntries" : [ + { + "argument" : "-com.apple.CoreData.ConcurrencyDebug 1" + } + ], + "environmentVariableEntries" : [ + { + "enabled" : false, + "key" : "LIBDISPATCH_COOPERATIVE_POOL_STRICT", + "value" : "1" + } + ], + "language" : "en", + "region" : "DE", + "testExecutionOrdering" : "random" + }, + "testTargets" : [ + { + "skippedTests" : [ + "AssetV3UploadRequestStrategyTests", + "AssetV3UploadRequestStrategyTests\/testThatItAddsAssetId_OnSuccessfulResponse()", + "AssetV3UploadRequestStrategyTests\/testThatItCancelsRequest_WhenTransferStateChangesToUploadingCancelled()", + "AssetV3UploadRequestStrategyTests\/testThatItDoesNotGenerateRequestForDeliveredMessages()", + "AssetV3UploadRequestStrategyTests\/testThatItDoesNotGenerateRequestForVersion2Assets()", + "AssetV3UploadRequestStrategyTests\/testThatItDoesNotGenerateRequestWhenTransferStateIsNotUploading()", + "AssetV3UploadRequestStrategyTests\/testThatItDoesNotGenerateRequestWhilePreprocessingIsNotCompleted()", + "AssetV3UploadRequestStrategyTests\/testThatItDoesNotUpdateTransferState_OnSuccessfulResponse_WhenThereIsMoreAssetsToUpload()", + "AssetV3UploadRequestStrategyTests\/testThatItExpiresTheMessage_OnPermanentFailureResponse()", + "AssetV3UploadRequestStrategyTests\/testThatItGeneratesRequestWhenAssetIsPreprocessed()", + "AssetV3UploadRequestStrategyTests\/testThatItUpdatesTransferState_OnSuccessfulResponse()", + "AssetV3UploadRequestStrategyTests\/testThatItUpdatesUploadProgress()", + "FetchClientRequestStrategyTests", + "FetchClientRequestStrategyTests\/testThatItAddsFetchedClientToIgnoredClientsWhenClientDoesNotExist()", + "FetchClientRequestStrategyTests\/testThatItAddsFetchedClientToIgnoredClientsWhenClientHasNoSession()", + "FetchClientRequestStrategyTests\/testThatItAddsFetchedClientToIgnoredClientsWhenSessionExistsButClientDoesNotExist()", + "FetchClientRequestStrategyTests\/testThatItAddsOtherUsersNewFetchedClientsToSelfUsersMissingClients()", + "FetchClientRequestStrategyTests\/testThatItCreatesABatchRequest_WhenUserClientNeedsToBeUpdatedFromBackend_AndDomainIsAvailble()", + "FetchClientRequestStrategyTests\/testThatItCreatesARequestForV0_WhenUserClientNeedsToBeUpdatedFromBackend()", + "FetchClientRequestStrategyTests\/testThatItCreatesARequestForV1_WhenUserClientNeedsToBeUpdatedFromBackend()", + "FetchClientRequestStrategyTests\/testThatItCreatesARequestForV2_WhenUserClientNeedsToBeUpdatedFromBackend()", + "FetchClientRequestStrategyTests\/testThatItCreatesARequestForV2_WhenUserClientNeedsToBeUpdatedFromBackend_AutomaticSync()", + "FetchClientRequestStrategyTests\/testThatItCreatesBatchRequestForV2_WhenFederationEndpointIsAvailable()", + "FetchClientRequestStrategyTests\/testThatItCreatesBatchRequest_WhenFederationEndpointIsAvailable()", + "FetchClientRequestStrategyTests\/testThatItCreatesLegacyRequest_WhenFederationEndpointIsNotAvailable()", + "FetchClientRequestStrategyTests\/testThatItCreatesOtherUsersClientsCorrectly()", + "FetchClientRequestStrategyTests\/testThatItDeletesAnObjectWhenResponseDoesNotContainRemoteID()", + "FetchClientRequestStrategyTests\/testThatItDeletesLocalClient_WhenNotIncludedInBatchResponse()", + "FetchClientRequestStrategyTests\/testThatItDeletesLocalClientsNotIncludedInResponseToFetchOtherUsersClients()", + "FetchClientRequestStrategyTests\/testThatItDeletesTheClient_WhenReceivingPermanentErrorResponse()", + "FetchClientRequestStrategyTests\/testThatItDoesNotDeleteAnObjectWhenResponseContainsRemoteID()", + "FetchClientRequestStrategyTests\/testThatItMarksNewClientsAsMissingAndIgnored_WhenReceivingTheBatchResponse()", + "FetchClientRequestStrategyTests\/testThatItUpdatesTheClient_WhenReceivingTheBatchResponse()", + "FetchClientRequestStrategyTests\/testThatItUpdatesTheClient_WhenReceivingTheResponse()", + "ZMLocalNotificationTests_Event\/testThatItDoesntCreateConversationCreateNotification_OneToOne()" + ], + "target" : { + "containerPath" : "container:WireRequestStrategy.xcodeproj", + "identifier" : "166901731D707509000FE4AF", + "name" : "WireRequestStrategyTests" + } + } + ], + "version" : 1 +} diff --git a/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/project.pbxproj b/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/project.pbxproj index af1125486cf..619754cf5e5 100644 --- a/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/project.pbxproj +++ b/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -41,6 +41,7 @@ 0649D19B24F66E9E001DDC78 /* ZMLocalNotification+Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0649D19A24F66E9E001DDC78 /* ZMLocalNotification+Events.swift */; }; 0649D19F24F6717C001DDC78 /* ZMLocalNotificationSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0649D19E24F6717C001DDC78 /* ZMLocalNotificationSet.swift */; }; 0649D1A624F673E7001DDC78 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0649D1A524F673E7001DDC78 /* Logging.swift */; }; + 066A3D822CDE6EFC0089C5C2 /* FetchBackendMLSPublicKeysRequestStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 066A3D812CDE6EE80089C5C2 /* FetchBackendMLSPublicKeysRequestStrategy.swift */; }; 0671976E2AE951F400D96598 /* AcmeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0671976D2AE951F400D96598 /* AcmeAPI.swift */; }; 0671979A2AFBE9FD00D96598 /* AcmeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 067197992AFBE9FD00D96598 /* AcmeResponse.swift */; }; 067215AD2B10DCA300CF4AD4 /* E2EIRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 067215AC2B10DCA300CF4AD4 /* E2EIRepository.swift */; }; @@ -196,6 +197,8 @@ 591B6E422C8B09B6009F8A7B /* WireRequestStrategy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1669016A1D707509000FE4AF /* WireRequestStrategy.framework */; }; 59271BED2B90D2140019B726 /* ClientRegistrationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59271BEC2B90D2140019B726 /* ClientRegistrationDelegate.swift */; }; 59271BF02B90D2510019B726 /* MockOTREntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59271BEE2B90D24A0019B726 /* MockOTREntity.swift */; }; + 59537D852CFF9D1600920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59537D842CFF9D1600920B59 /* WireLogging */; }; + 59537D872CFF9DF600920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59537D862CFF9DF600920B59 /* WireLogging */; }; 597B70C92B03CC76006C2121 /* UpdateConversationProtocolActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597B70C82B03CC76006C2121 /* UpdateConversationProtocolActionHandler.swift */; }; 597B70CC2B03CC83006C2121 /* UpdateConversationProtocolActionHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597B70CA2B03CC7D006C2121 /* UpdateConversationProtocolActionHandlerTests.swift */; }; 598D04302C89C67E00B64D71 /* WireFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 598D042F2C89C67E00B64D71 /* WireFoundation */; }; @@ -539,6 +542,7 @@ 0649D19A24F66E9E001DDC78 /* ZMLocalNotification+Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMLocalNotification+Events.swift"; sourceTree = ""; }; 0649D19E24F6717C001DDC78 /* ZMLocalNotificationSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZMLocalNotificationSet.swift; sourceTree = ""; }; 0649D1A524F673E7001DDC78 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + 066A3D812CDE6EE80089C5C2 /* FetchBackendMLSPublicKeysRequestStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchBackendMLSPublicKeysRequestStrategy.swift; sourceTree = ""; }; 0671976D2AE951F400D96598 /* AcmeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcmeAPI.swift; sourceTree = ""; }; 067197992AFBE9FD00D96598 /* AcmeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcmeResponse.swift; sourceTree = ""; }; 067215AC2B10DCA300CF4AD4 /* E2EIRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = E2EIRepository.swift; sourceTree = ""; }; @@ -942,12 +946,27 @@ F963E8D91D955D4600098AD3 /* AssetRequestFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetRequestFactory.swift; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 59D264292CF722680005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + AllTests.xctestplan, + ); + target = 166901731D707509000FE4AF /* WireRequestStrategyTests */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 59D264282CF7225A0005317F /* TestPlans */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (59D264292CF722680005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TestPlans; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 166901661D707509000FE4AF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 598D04332C89C6CF00B64D71 /* WireFoundation in Frameworks */, + 59537D852CFF9D1600920B59 /* WireLogging in Frameworks */, 591B6E3B2C8B09AA009F8A7B /* WireDataModel.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -960,6 +979,7 @@ CB7979052C73663B006FBA58 /* WireRequestStrategySupport.framework in Frameworks */, 598D04302C89C67E00B64D71 /* WireFoundation in Frameworks */, 598E870D2BF4E08100FC5438 /* WireUtilitiesSupport.framework in Frameworks */, + 59537D872CFF9DF600920B59 /* WireLogging in Frameworks */, CB5120532C6FD69F000C8FEC /* WireTransportSupport.framework in Frameworks */, 591B6E3D2C8B09AE009F8A7B /* WireDataModelSupport.framework in Frameworks */, ); @@ -1342,6 +1362,7 @@ F154EDC51F447B6C00CB8184 /* Test Host */, 1621D2211D75AB2D007108C2 /* Helpers */, F12CD7932035FC4400EAAEBC /* Resources */, + 59D264282CF7225A0005317F /* TestPlans */, ); path = Tests; sourceTree = ""; @@ -1743,6 +1764,7 @@ children = ( 7AEBB6732859E5F00090B524 /* Actions */, EEE0EDB02858906500BBEE29 /* MLSRequestStrategy.swift */, + 066A3D812CDE6EE80089C5C2 /* FetchBackendMLSPublicKeysRequestStrategy.swift */, ); path = MLS; sourceTree = ""; @@ -2190,6 +2212,7 @@ name = WireRequestStrategy; packageProductDependencies = ( 598D04322C89C6CF00B64D71 /* WireFoundation */, + 59537D842CFF9D1600920B59 /* WireLogging */, ); productName = WireRequestStrategy; productReference = 1669016A1D707509000FE4AF /* WireRequestStrategy.framework */; @@ -2210,9 +2233,13 @@ F154EDD71F447BB600CB8184 /* PBXTargetDependency */, CB7979082C73663B006FBA58 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 59D264282CF7225A0005317F /* TestPlans */, + ); name = WireRequestStrategyTests; packageProductDependencies = ( 598D042F2C89C67E00B64D71 /* WireFoundation */, + 59537D862CFF9DF600920B59 /* WireLogging */, ); productName = WireRequestStrategyTests; productReference = 166901741D707509000FE4AF /* WireRequestStrategyTests.xctest */; @@ -2521,6 +2548,7 @@ E629E3A32B47015300D526AD /* EventData.swift in Sources */, 63CC83E2285C9C6C008549AD /* UploadSelfMLSKeyPackagesActionHandler.swift in Sources */, 166901D41D7081C7000FE4AF /* ZMDownstreamObjectSync.m in Sources */, + 066A3D822CDE6EFC0089C5C2 /* FetchBackendMLSPublicKeysRequestStrategy.swift in Sources */, EE7F02272A80E5F400FE5695 /* CreateGroupConversationActionHandler.swift in Sources */, E629E39F2B4700B600D526AD /* Payload+NewConversation.swift in Sources */, 0649D14524F63BBD001DDC78 /* LocalNotificationType+Configuration.swift in Sources */, @@ -3195,6 +3223,14 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ + 59537D842CFF9D1600920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; + 59537D862CFF9DF600920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; 598D042F2C89C67E00B64D71 /* WireFoundation */ = { isa = XCSwiftPackageProductDependency; productName = WireFoundation; diff --git a/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 3ddf867a10a..dc8d12300fe 100644 --- a/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -4,5 +4,7 @@ BuildSystemType Latest + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + diff --git a/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/xcshareddata/xcschemes/WireRequestStrategy.xcscheme b/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/xcshareddata/xcschemes/WireRequestStrategy.xcscheme index 62abbae2ced..44233199c55 100644 --- a/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/xcshareddata/xcschemes/WireRequestStrategy.xcscheme +++ b/wire-ios-request-strategy/WireRequestStrategy.xcodeproj/xcshareddata/xcschemes/WireRequestStrategy.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1610" + version = "1.7"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> - - - - - - - - - - - - - - - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + - - - - - - - - - - - - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - -{one line to give the program's name and a brief idea of what it does.} -Copyright (C) {year} {name of author} - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -{project} Copyright (C) {year} {fullname} -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - -The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/wire-ios-share-engine/README.md b/wire-ios-share-engine/README.md deleted file mode 100644 index 83bb5570ae3..00000000000 --- a/wire-ios-share-engine/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Wire™ - -[![Wire logo](https://github.com/wireapp/wire/blob/master/assets/header-small.png?raw=true)](https://wire.com/jobs/) - -[![Azure Pipelines Build Status](https://dev.azure.com/wireswiss/Wire%20iOS/_apis/build/status/Frameworks/wire-ios-share-engine?branchName=develop)](https://dev.azure.com/wireswiss/Wire%20iOS/_build/latest?definitionId=18&branchName=develop) [![codecov](https://codecov.io/gh/wireapp/wire-ios-share-engine/branch/develop/graph/badge.svg)](https://codecov.io/gh/wireapp/wire-ios-share-engine) - -This repository is part of the source code of Wire. You can find more information at [wire.com](https://wire.com) or by contacting opensource@wire.com. - -You can find the published source code at [github.com/wireapp/wire](https://github.com/wireapp/wire). - -For licensing information, see the attached LICENSE file and the list of third-party licenses at [wire.com/legal/licenses/](https://wire.com/legal/licenses/). - -# wire-ios-share-engine - -This framework is part of Wire iOS. Additional documentation is available in the [Wire iOS wiki](https://github.com/wireapp/wire-ios/wiki). diff --git a/wire-ios-share-engine/Sources/SharingSession.swift b/wire-ios-share-engine/Sources/SharingSession.swift index 5e0ec63dca9..4947b5fae41 100644 --- a/wire-ios-share-engine/Sources/SharingSession.swift +++ b/wire-ios-share-engine/Sources/SharingSession.swift @@ -356,7 +356,8 @@ public final class SharingSession { coreDataStack.syncContext.proteusService = proteusService } - if DeveloperFlag.enableMLSSupport.isOn, coreDataStack.syncContext.mlsDecryptionService == nil { + let mlsFeature = FeatureRepository(context: coreDataStack.syncContext).fetchMLS() + if mlsFeature.isEnabled, coreDataStack.syncContext.mlsDecryptionService == nil { coreDataStack.syncContext.mlsDecryptionService = mlsDecryptionService } } diff --git a/wire-ios-share-engine/Sources/ZMConversation+Conversation.swift b/wire-ios-share-engine/Sources/ZMConversation+Conversation.swift index f0efcc307f2..91473c5555b 100644 --- a/wire-ios-share-engine/Sources/ZMConversation+Conversation.swift +++ b/wire-ios-share-engine/Sources/ZMConversation+Conversation.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging import WireRequestStrategy import WireUtilities diff --git a/wire-ios-share-engine/Sources/ZMMessage+Sendable.swift b/wire-ios-share-engine/Sources/ZMMessage+Sendable.swift index c686a1be93a..43d47f0cb36 100644 --- a/wire-ios-share-engine/Sources/ZMMessage+Sendable.swift +++ b/wire-ios-share-engine/Sources/ZMMessage+Sendable.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging private extension ZMMessage { diff --git a/wire-ios-share-engine/Tests/TestPlans/AllTests.xctestplan b/wire-ios-share-engine/Tests/TestPlans/AllTests.xctestplan new file mode 100644 index 00000000000..24112f9dbfe --- /dev/null +++ b/wire-ios-share-engine/Tests/TestPlans/AllTests.xctestplan @@ -0,0 +1,26 @@ +{ + "configurations" : [ + { + "id" : "37260901-6711-4495-BF17-09C709CE3A4F", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "language" : "en", + "region" : "DE", + "testExecutionOrdering" : "random" + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:WireShareEngine.xcodeproj", + "identifier" : "5470D3F11D76E1B000FDE440", + "name" : "WireShareEngineTests" + } + } + ], + "version" : 1 +} diff --git a/wire-ios-share-engine/WireShareEngine.xcodeproj/project.pbxproj b/wire-ios-share-engine/WireShareEngine.xcodeproj/project.pbxproj index 56714fa526d..eeb27fe5db9 100644 --- a/wire-ios-share-engine/WireShareEngine.xcodeproj/project.pbxproj +++ b/wire-ios-share-engine/WireShareEngine.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -13,6 +13,7 @@ 591B6E322C8B098C009F8A7B /* WireRequestStrategy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE668BC12954B82900D939E7 /* WireRequestStrategy.framework */; }; 591B6E352C8B0991009F8A7B /* WireDataModelSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E6E504512BC58ACD004948E7 /* WireDataModelSupport.framework */; }; 591B6E382C8B0995009F8A7B /* WireMockTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE67F6F8296F094C001D7C88 /* WireMockTransport.framework */; }; + 59537D8B2CFF9F8F00920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59537D8A2CFF9F8F00920B59 /* WireLogging */; }; BFA18BD41D806050005C281B /* BaseSharingSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA18BD31D806050005C281B /* BaseSharingSessionTests.swift */; }; CB79791D2C748580006FBA58 /* TestSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB79791C2C748580006FBA58 /* TestSetup.swift */; }; CE7FBFC41E015C5900E1C4C9 /* RequestGeneratorStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7FBFC31E015C5900E1C4C9 /* RequestGeneratorStoreTests.swift */; }; @@ -66,8 +67,6 @@ 5470D4121D76E2A400FDE440 /* project.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = project.xcconfig; sourceTree = ""; }; 5470D4151D76E2A400FDE440 /* tests.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = tests.xcconfig; sourceTree = ""; }; 5470D4171D76E2A400FDE440 /* warnings.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = warnings.xcconfig; sourceTree = ""; }; - 5470D42B1D770CEE00FDE440 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; - 5470D42C1D770CEE00FDE440 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; BFA18BD31D806050005C281B /* BaseSharingSessionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseSharingSessionTests.swift; sourceTree = ""; }; CB79791C2C748580006FBA58 /* TestSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSetup.swift; sourceTree = ""; }; CE7FBFC31E015C5900E1C4C9 /* RequestGeneratorStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestGeneratorStoreTests.swift; sourceTree = ""; }; @@ -95,12 +94,27 @@ F1DABFA11E9B8B6100AD2324 /* ZMMessage+Sendable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ZMMessage+Sendable.swift"; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 59D264242CF722020005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + TestPlans/AllTests.xctestplan, + ); + target = 5470D3E71D76E1B000FDE440 /* WireShareEngine */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 59D264222CF721F70005317F /* Tests */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (59D264242CF722020005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Tests; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 5470D3E41D76E1B000FDE440 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 591B6E322C8B098C009F8A7B /* WireRequestStrategy.framework in Frameworks */, + 59537D8B2CFF9F8F00920B59 /* WireLogging in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -129,9 +143,8 @@ isa = PBXGroup; children = ( EE9AEC952BD159A900F7853F /* WireShareEngine.docc */, - 5470D42B1D770CEE00FDE440 /* LICENSE */, - 5470D42C1D770CEE00FDE440 /* README.md */, F1DABF961E9B8B6100AD2324 /* Sources */, + 59D264222CF721F70005317F /* Tests */, 5470D3F61D76E1B000FDE440 /* WireShareEngineTests */, 5470D4021D76E2A400FDE440 /* Resources */, 5470D3E91D76E1B000FDE440 /* Products */, @@ -267,6 +280,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 59D264222CF721F70005317F /* Tests */, + ); name = WireShareEngine; productName = "wire-ios-share-engine"; productReference = 5470D3E81D76E1B000FDE440 /* WireShareEngine.framework */; @@ -518,7 +534,10 @@ baseConfigurationReference = 5470D40B1D76E2A400FDE440 /* ios-test-host.xcconfig */; buildSettings = { INFOPLIST_FILE = "$(SRCROOT)/WireShareEngineTests/WireShareEngineTestHost/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.wire.WireShareEngineTestHost; PRODUCT_NAME = "$(TARGET_NAME)"; }; @@ -529,7 +548,10 @@ baseConfigurationReference = 5470D40B1D76E2A400FDE440 /* ios-test-host.xcconfig */; buildSettings = { INFOPLIST_FILE = "$(SRCROOT)/WireShareEngineTests/WireShareEngineTestHost/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.wire.WireShareEngineTestHost; PRODUCT_NAME = "$(TARGET_NAME)"; }; @@ -575,6 +597,13 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + 59537D8A2CFF9F8F00920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 5470D3DF1D76E1B000FDE440 /* Project object */; } diff --git a/wire-ios-share-engine/WireShareEngine.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wire-ios-share-engine/WireShareEngine.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 3ddf867a10a..dc8d12300fe 100644 --- a/wire-ios-share-engine/WireShareEngine.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/wire-ios-share-engine/WireShareEngine.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -4,5 +4,7 @@ BuildSystemType Latest + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + diff --git a/wire-ios-share-engine/WireShareEngine.xcodeproj/xcshareddata/xcschemes/WireShareEngine.xcscheme b/wire-ios-share-engine/WireShareEngine.xcodeproj/xcshareddata/xcschemes/WireShareEngine.xcscheme index a4679abbe0f..65abc61edc6 100644 --- a/wire-ios-share-engine/WireShareEngine.xcodeproj/xcshareddata/xcschemes/WireShareEngine.xcscheme +++ b/wire-ios-share-engine/WireShareEngine.xcodeproj/xcshareddata/xcschemes/WireShareEngine.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1610" + version = "1.7"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> - - - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + - - - - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - -{one line to give the program's name and a brief idea of what it does.} -Copyright (C) {year} {name of author} - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -{project} Copyright (C) {year} {fullname} -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - -The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/wire-ios-sync-engine/README.md b/wire-ios-sync-engine/README.md deleted file mode 100644 index a337efe7ad1..00000000000 --- a/wire-ios-sync-engine/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Wire™ - -[![Wire logo](https://github.com/wireapp/wire/blob/master/assets/header-small.png?raw=true)](https://wire.com/jobs/) - - -[![Azure Pipelines Build Status](https://dev.azure.com/wireswiss/Wire%20iOS/_apis/build/status/Frameworks/wire-ios-sync-engine?branchName=develop)](https://dev.azure.com/wireswiss/Wire%20iOS/_build/latest?definitionId=31&branchName=develop) [![codecov](https://codecov.io/gh/wireapp/wire-ios-sync-engine/branch/develop/graph/badge.svg)](https://codecov.io/gh/wireapp/wire-ios-sync-engine) - -This repository is part of the source code of Wire. You can find more information at [wire.com](https://wire.com) or by contacting opensource@wire.com. - -You can find the published source code at [github.com/wireapp/wire](https://github.com/wireapp/wire). - -# Wire iOS Sync Engine - -The wire-ios-sync-engine framework is used as part of the [Wire iOS client](http://github.com/wireapp/wire-ios) and is the top-most layer of the underlying *sync engine*. It is using a number of lower-level frameworks. - -The Wire iOS sync engine is developed in a mix of Objective-C and Swift (and just a handful of classes in Objective-C++). It is a result of a long development process that was started in Objective-C when Swift was not yet available. In the past years, parts of it have been written or rewritten in Swift. Going forward, expect new functionalities to be developed almost exclusively in Swift. - -## Documentation -Additional documentation is available in the [Wire iOS wiki](https://github.com/wireapp/wire-ios/wiki). - -# How to build - -*iOS SyncEngine* is build with Xcode 13.1 using Swift 5. - -It is using [Carthage](https://github.com/Carthage/Carthage) to manage dependencies. To pull the dependencies binaries run `carthage bootstrap --platform ios --use-xcframeworks`. - -You can now open the Xcode project and build. diff --git a/wire-ios-sync-engine/Source/Calling/CallKitManager.swift b/wire-ios-sync-engine/Source/Calling/CallKitManager.swift index a84adf50b68..7fef5120f45 100644 --- a/wire-ios-sync-engine/Source/Calling/CallKitManager.swift +++ b/wire-ios-sync-engine/Source/Calling/CallKitManager.swift @@ -20,6 +20,7 @@ import avs import CallKit import Foundation import Intents +import WireLogging import WireRequestStrategy protocol CallKitManagerDelegate: AnyObject { diff --git a/wire-ios-sync-engine/Source/Calling/MLSConferenceStaleParticipantsRemover.swift b/wire-ios-sync-engine/Source/Calling/MLSConferenceStaleParticipantsRemover.swift index ba232058d4c..af9b959a3d0 100644 --- a/wire-ios-sync-engine/Source/Calling/MLSConferenceStaleParticipantsRemover.swift +++ b/wire-ios-sync-engine/Source/Calling/MLSConferenceStaleParticipantsRemover.swift @@ -19,6 +19,7 @@ import Combine import Foundation import WireDataModel +import WireLogging import WireUtilities /// A class responsible for removing stale participants in a MLS conference. diff --git a/wire-ios-sync-engine/Source/Calling/WireCallCenterV3+Events.swift b/wire-ios-sync-engine/Source/Calling/WireCallCenterV3+Events.swift index bc7c4963016..8014f3ee1c4 100644 --- a/wire-ios-sync-engine/Source/Calling/WireCallCenterV3+Events.swift +++ b/wire-ios-sync-engine/Source/Calling/WireCallCenterV3+Events.swift @@ -18,6 +18,7 @@ import avs import Foundation +import WireLogging // MARK: Conversation Changes diff --git a/wire-ios-sync-engine/Source/Calling/WireCallCenterV3+MLS.swift b/wire-ios-sync-engine/Source/Calling/WireCallCenterV3+MLS.swift index db8e1b2813f..d83281e1623 100644 --- a/wire-ios-sync-engine/Source/Calling/WireCallCenterV3+MLS.swift +++ b/wire-ios-sync-engine/Source/Calling/WireCallCenterV3+MLS.swift @@ -19,6 +19,7 @@ import Combine import Foundation import WireDataModel +import WireLogging struct ConferenceParticipantsInfo { let participants: [CallParticipant] diff --git a/wire-ios-sync-engine/Source/Calling/WireCallCenterV3.swift b/wire-ios-sync-engine/Source/Calling/WireCallCenterV3.swift index abf222c7ee9..3d5ec67e29d 100644 --- a/wire-ios-sync-engine/Source/Calling/WireCallCenterV3.swift +++ b/wire-ios-sync-engine/Source/Calling/WireCallCenterV3.swift @@ -19,8 +19,7 @@ import avs import Combine import Foundation - -private let zmLog = ZMSLog(tag: "calling") +import WireLogging /// WireCallCenter is used for making Wire calls and observing their state. There can only be one instance of the /// WireCallCenter. @@ -942,7 +941,7 @@ extension WireCallCenterV3 { overMLSSelfConversation: Bool = false ) { Self.logger.info("sending call message for AVS") - zmLog.debug("\(self): send call message, transport = \(String(describing: transport))") + Self.logger.debug("\(self): send call message, transport = \(String(describing: transport))") transport?.send( data: data, conversationId: conversationId, @@ -957,10 +956,10 @@ extension WireCallCenterV3 { /// Sends an SFT call message when requested by AVS through `wcall_sft_req_h`. func sendSFT(token: WireCallMessageToken, url: String, data: Data) { Self.logger.info("sending SFT message for AVS") - zmLog.debug("\(self): send SFT call message, transport = \(String(describing: transport))") + Self.logger.debug("\(self): send SFT call message, transport = \(String(describing: transport))") guard let endpoint = URL(string: url) else { - zmLog.error("SFT request failed. Invalid url: \(url)") + Self.logger.error("SFT request failed. Invalid url: \(url)") avsWrapper.handleSFTResponse(data: nil, context: token) return } @@ -968,7 +967,7 @@ extension WireCallCenterV3 { transport?.sendSFT(data: data, url: endpoint) { [weak self] result in switch result { case let .failure(error): - zmLog.error("SFT request failed: \(error.localizedDescription)") + Self.logger.error("SFT request failed: \(error.localizedDescription)") self?.avsWrapper.handleSFTResponse(data: nil, context: token) case let .success(data): @@ -979,17 +978,17 @@ extension WireCallCenterV3 { /// Sends the config request when requested by AVS through `wcall_config_req_h`. func requestCallConfig() { - zmLog.debug("\(self): requestCallConfig(), transport = \(String(describing: transport))") + Self.logger.debug("\(self): requestCallConfig(), transport = \(String(describing: transport))") transport?.requestCallConfig(completionHandler: { [weak self] config, httpStatusCode in guard let self else { return } - zmLog.debug("\(self): self.avsWrapper.update with \(String(describing: config))") + Self.logger.debug("\(self): self.avsWrapper.update with \(String(describing: config))") avsWrapper.update(callConfig: config, httpStatusCode: httpStatusCode) }) } /// Tags a call as missing when requested by AVS through `wcall_missed_h`. func missed(conversationId: AVSIdentifier, userId: AVSIdentifier, timestamp: Date, isVideoCall: Bool) { - zmLog.debug("missed call") + Self.logger.debug("missed call") if let context = uiMOC { WireCallCenterMissedCallNotification( diff --git a/wire-ios-sync-engine/Source/Data Model/Conversation+AccessMode.swift b/wire-ios-sync-engine/Source/Data Model/Conversation+AccessMode.swift index df55ca46950..670f76d58db 100644 --- a/wire-ios-sync-engine/Source/Data Model/Conversation+AccessMode.swift +++ b/wire-ios-sync-engine/Source/Data Model/Conversation+AccessMode.swift @@ -233,7 +233,7 @@ enum WirelessRequestFactory { switch apiVersion { - case .v3, .v4, .v5, .v6: + case .v3, .v4, .v5, .v6, .v7: let domain = if let domain = conversation.domain, !domain.isEmpty { domain } else { BackendInfo.domain } guard let domain else { fatal("no domain associated with conversation, can't make the request") diff --git a/wire-ios-sync-engine/Source/Data Model/Conversation+Deletion.swift b/wire-ios-sync-engine/Source/Data Model/Conversation+Deletion.swift index 8ad5d035e28..52240ff958b 100644 --- a/wire-ios-sync-engine/Source/Data Model/Conversation+Deletion.swift +++ b/wire-ios-sync-engine/Source/Data Model/Conversation+Deletion.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging import WireSystem public enum ConversationDeletionError: Error { diff --git a/wire-ios-sync-engine/Source/Data Model/ServiceUser.swift b/wire-ios-sync-engine/Source/Data Model/ServiceUser.swift index 79243624170..aba0f256d87 100644 --- a/wire-ios-sync-engine/Source/Data Model/ServiceUser.swift +++ b/wire-ios-sync-engine/Source/Data Model/ServiceUser.swift @@ -113,7 +113,9 @@ private extension ServiceUserData { fatal("conversation is not synced with the backend") } - let path = "/conversations/\(remoteIdentifier.transportString())/bots" + let path = apiVersion >= .v7 + ? "/bot/conversations/\(remoteIdentifier.transportString())" + : "/conversations/\(remoteIdentifier.transportString())/bots" let payload: NSDictionary = [ "provider": provider.transportString(), diff --git a/wire-ios-sync-engine/Source/E2EI/CRL/CertificateRevocationListsChecker.swift b/wire-ios-sync-engine/Source/E2EI/CRL/CertificateRevocationListsChecker.swift index 842a0853553..28560fdaf8f 100644 --- a/wire-ios-sync-engine/Source/E2EI/CRL/CertificateRevocationListsChecker.swift +++ b/wire-ios-sync-engine/Source/E2EI/CRL/CertificateRevocationListsChecker.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging // sourcery: AutoMockable public protocol CertificateRevocationListsChecking { diff --git a/wire-ios-sync-engine/Source/Registration/AddressBook.swift b/wire-ios-sync-engine/Source/Registration/AddressBook.swift deleted file mode 100644 index 5f0728868ed..00000000000 --- a/wire-ios-sync-engine/Source/Registration/AddressBook.swift +++ /dev/null @@ -1,349 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Contacts -import Foundation -import libPhoneNumberiOS - -/// Wraps the system address book to return `ZMAddressBookContact` when iterating, filtering out those -/// without a valid email or phone -protocol AddressBookAccessor { - - /// Number of contacts in the address book - var numberOfContacts: UInt { get } - - /// Enumerates the contacts whitout performing any normalization or validation, invoking the block for each contact. - /// If the block returns false, it will stop enumerating them. - func enumerateRawContacts(block: @escaping (ContactRecord) -> (Bool)) - - /// Returns contacts matching search query - func rawContacts(matchingQuery: String) -> [ContactRecord] - - /// Normalization function for phone numbers - var phoneNumberNormalizer: AddressBook.Normalizer { get } - - /// Gets a specific address book user by the local address book indentifier - func contact(identifier: String) -> ContactRecord? -} - -extension AddressBookAccessor { - - /// Enumerates the contacts, normalized and validated, invoking the block for each contact. - /// Non valid contacts (no email nor phone) will be excluded from the enumeration. - /// If the block returns false, it will stop enumerating them. - func enumerateValidContacts(block: @escaping (ZMAddressBookContact) -> (Bool)) { - enumerateRawContacts { - guard let parsed = ZMAddressBookContact(contact: $0, phoneNumberNormalizer: self.phoneNumberNormalizer) - else { - return true - } - return block(parsed) - } - } - - /// Returns valid contacts matching the search query, with normalized email and phone numbers - func contacts(matchingQuery: String) -> [ZMAddressBookContact] { - rawContacts(matchingQuery: matchingQuery) - .compactMap { ZMAddressBookContact(contact: $0, phoneNumberNormalizer: self.phoneNumberNormalizer) } - } - - /// Encodes an arbitraty part the address book asynchronously. Will invoke the completion handler when done. - /// - parameter groupQueue: group queue to enter while executing, and where to invoke callback - /// - parameter completion: closure invoked when the address book encoding ended. It will receive nil parameter - /// if there are no contacts to upload - /// - parameter maxNumberOfContacts: do not include more than this number of contacts - /// - parameter startingContactIndex: include contacts starting from this index in the address book - func encodeWithCompletionHandler( - _ groupQueue: GroupQueue, - startingContactIndex: UInt, - maxNumberOfContacts: UInt, - completion: @escaping (EncodedAddressBookChunk?) -> Void - ) { - // here we are explicitly capturing self, this is executed on a queue that is - // never blocked indefinitely as this is the only function using it - groupQueue.dispatchGroup?.async(on: addressBookProcessingQueue) { - - let range: Range = startingContactIndex ..< (startingContactIndex + maxNumberOfContacts) - let cards = self.generateContactCards(range: range) - - guard !cards.isEmpty || startingContactIndex > 0 else { - // this should happen if I have zero contacts - groupQueue.performGroupedBlock { - completion(nil) - } - return - } - - let cardsRange = startingContactIndex ..< (startingContactIndex + UInt(cards.count)) - let encodedAB = EncodedAddressBookChunk( - numberOfTotalContacts: self.numberOfContacts, - otherContactsHashes: cards, - includedContacts: cardsRange - ) - groupQueue.performGroupedBlock { - completion(encodedAB) - } - } - } - - /// Generate contact cards for the given range of contacts - private func generateContactCards(range: Range) -> [String: [String]] { - var cards = [String: [String]]() - - contacts(range: range).enumerated().forEach { - let contact = $0.element - cards[contact.localIdentifier ?? "\($0.offset)"] = (contact.emailAddresses.map(\.base64EncodedSHADigest)) - + (contact.phoneNumbers.map(\.base64EncodedSHADigest)) - } - return cards - } - - /// Returns contacts in a specific range - func contacts(range: Range) -> [ZMAddressBookContact] { - var contacts = [ZMAddressBookContact]() - - let maxElements = Int(range.upperBound - range.lowerBound) - contacts.reserveCapacity(maxElements) - - var skipped: UInt = 0 - enumerateValidContacts { contact -> (Bool) in - if skipped < range.lowerBound { - skipped += 1 - return true - } - contacts.append(contact) - return contacts.count < maxElements - } - - return contacts - } - - /// Returns the first X raw contacts from the address book - func firstRawContacts(number: Int) -> [ContactRecord] { - var contacts = [ContactRecord]() - contacts.reserveCapacity(number) - var count = 0 - enumerateRawContacts { record in - contacts.append(record) - count += 1 - return count < number - } - return contacts - } -} - -/// Common base class between iOS 8 (AddressBook framework) and iOS 9+ (Contacts framework) -class AddressBook { - - /// normalizer for phone numbers - let phoneNumberNormalizer: AddressBook.Normalizer - - init() { - let libPhoneNumber = NBPhoneNumberUtil.sharedInstance() - self.phoneNumberNormalizer = { libPhoneNumber?.normalize(phoneNumber: $0)?.validatedPhoneNumber } - } - - typealias Normalizer = (String) -> (String?) - typealias AccessCheck = () -> (Bool) - - /// Will return an instance of the address book accessor best suited for the - /// current OS version. Will return `nil` if the user did not grant access to the AB - static func factory() -> AddressBookAccessor? { - guard accessGranted() else { - return nil - } - - return ContactAddressBook() - } - - /// Uses the passed in closure, or the standard method if the closure is nil, to - /// check if the AB access was granted. Returns whether it was granted. - static func accessGranted(_ checkClosure: AccessCheck? = nil) -> Bool { - if let closure = checkClosure { - return closure() - } - return CNContactStore.authorizationStatus(for: .contacts) == .authorized - } -} - -// MARK: - Encoded address book chunk - -struct EncodedAddressBookChunk { - - /// Total number of contacts in the address book - let numberOfTotalContacts: UInt - - /// Data to upload for contacts other that the self user - /// maps from contact ID to hashes - let otherContactsHashes: [String: [String]] - - /// Contacts included in this chunck, according to AB order - let includedContacts: CountableRange -} - -// MARK: - Phone number and email normalization - -private extension NBPhoneNumberUtil { - - /// Returns a normalized version of the phone number, or nil - /// if the phone number was not normalizable. - /// - note: numbers starting with "+0", a prefix that is not - /// assigned to any real number, are considered test numbers - /// used for QA automation and will always be accepted, without being - /// normalized through the normalization library but just sanitized - /// from any non-numberic character - func normalize(phoneNumber: String) -> String? { - let testingNumberPrefix = "+0" - guard !phoneNumber.hasPrefix(testingNumberPrefix) else { - return phoneNumber.validatedPhoneNumber - } - - guard let parsedNumber = try? parse(withPhoneCarrierRegion: phoneNumber) else { - return nil - } - guard let normalizedNumber = try? format(parsedNumber, numberFormat: .E164) else { - return nil - } - return normalizedNumber - } - -} - -extension String { - - /// Returns a normalized phone number or nil - var validatedPhoneNumber: String? { - - // allow +0 numbers - if hasPrefix("+0") { - return "+" + (components(separatedBy: CharacterSet.decimalDigits.inverted) - .joined(separator: "")) // remove all non-digit - } - - var number: Any? = self as Any? - do { - try ZMPhoneNumberValidator.validateValue(&number) - return number as? String - } catch { - return nil - } - } - - /// Returns a normalized email or nil - var validatedEmail: String? { - var email: Any? = self as Any? - do { - try ZMEmailAddressValidator.validateValue(&email) - return email as? String - } catch { - return nil - } - } -} - -// MARK: - Utilities - -let addressBookContactsSearchLimit = 2000 - -extension String { - - /// Returns the base64 encoded string of the SHA hash of the string - var base64EncodedSHADigest: String { - Data(utf8).zmSHA256Digest().base64EncodedString(options: []) - } - -} - -/// Private AB processing queue -let addressBookProcessingQueue = DispatchQueue(label: "Address book processing", attributes: []) - -extension Sequence { - - /// Returns the elements of the sequence in the positions indicated by the range - func elements(_ range: Range) -> AnyIterator { - - var generator = makeIterator() - var count: UInt = 0 - - return AnyIterator { - - while count < range.lowerBound { - if generator.next() != nil { - count += 1 - continue - } else { - return nil - } - } - if count == range.upperBound { - return nil - } - count += 1 - return generator.next() - } - } -} - -// Generic contactto abstract actual address book framework details -protocol ContactRecord { - - var rawEmails: [String] { get } - var rawPhoneNumbers: [String] { get } - var firstName: String { get } - var lastName: String { get } - var middleName: String { get } - var nickname: String { get } - var organization: String { get } - var localIdentifier: String { get } - -} - -extension ContactRecord { - - var displayName: String { - [firstName, middleName, lastName] - .filter { $0 != "" } - .joined(separator: " ") - } -} - -extension ZMAddressBookContact { - - convenience init?( - contact: ContactRecord, - phoneNumberNormalizer: AddressBook.Normalizer - ) { - self.init() - - // names - self.firstName = contact.firstName - self.lastName = contact.lastName - self.middleName = contact.middleName - self.nickname = contact.nickname - self.organization = contact.organization - self.emailAddresses = contact.rawEmails.compactMap(\.validatedEmail) - self.rawPhoneNumbers = contact.rawPhoneNumbers - self.phoneNumbers = rawPhoneNumbers.compactMap { phoneNumberNormalizer($0) } - self.localIdentifier = contact.localIdentifier - - // ignore contacts with no email nor phones - guard !emailAddresses.isEmpty || !phoneNumbers.isEmpty else { - return nil - } - } -} diff --git a/wire-ios-sync-engine/Source/Registration/ContactAddressBook.swift b/wire-ios-sync-engine/Source/Registration/ContactAddressBook.swift deleted file mode 100644 index 4596da4a5f7..00000000000 --- a/wire-ios-sync-engine/Source/Registration/ContactAddressBook.swift +++ /dev/null @@ -1,143 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Contacts -import Foundation - -private let zmLog = ZMSLog(tag: "ContactAddressBook") - -/// iOS Contacts-based address book -final class ContactAddressBook: AddressBook { - - let store = CNContactStore() -} - -extension ContactAddressBook: AddressBookAccessor { - - /// Gets a specific address book user by the local address book indentifier - func contact(identifier: String) -> ContactRecord? { - try? store.unifiedContact(withIdentifier: identifier, keysToFetch: ContactAddressBook.keysToFetch) - } - - static var keysToFetch: [CNKeyDescriptor] { - [ - CNContactPhoneNumbersKey as CNKeyDescriptor, - CNContactEmailAddressesKey as CNKeyDescriptor, - CNContactFormatter.descriptorForRequiredKeys(for: .fullName), - CNContactOrganizationNameKey as CNKeyDescriptor - ] - } - - func rawContacts(matchingQuery query: String) -> [ContactRecord] { - guard AddressBook.accessGranted() else { - return [] - } - - guard !query.isEmpty else { - return firstRawContacts(number: addressBookContactsSearchLimit) - } - - let predicate: NSPredicate = CNContact.predicateForContacts(matchingName: query.lowercased()) - guard let foundContacts = try? CNContactStore().unifiedContacts( - matching: predicate, - keysToFetch: ContactAddressBook.keysToFetch - ) else { - return [] - } - return foundContacts - } - - /// Enumerates the contacts, invoking the block for each contact. - /// If the block returns false, it will stop enumerating them. - func enumerateRawContacts(block: @escaping (ContactRecord) -> (Bool)) { - let request = CNContactFetchRequest(keysToFetch: ContactAddressBook.keysToFetch) - request.sortOrder = .userDefault - do { - try store.enumerateContacts(with: request) { contact, stop in - let shouldContinue = block(contact) - stop.initialize(to: ObjCBool(!shouldContinue)) - } - } catch { - zmLog.error(error.localizedDescription) - } - } - - /// Number of contacts in the address book - var numberOfContacts: UInt { - 0 - } -} - -extension CNContact: ContactRecord { - - var rawEmails: [String] { - emailAddresses.map { $0.value as String } - } - - var rawPhoneNumbers: [String] { - phoneNumbers.map(\.value.stringValue) - } - - var firstName: String { - givenName - } - - var lastName: String { - familyName - } - - var organization: String { - organizationName - } - - var localIdentifier: String { - identifier - } -} - -extension ZMAddressBookContact { - - convenience init?( - contact: CNContact, - phoneNumberNormalizer: @escaping AddressBook.Normalizer, - emailNormalizer: @escaping AddressBook.Normalizer - ) { - self.init() - - // names - self.firstName = contact.givenName - self.lastName = contact.familyName - self.middleName = contact.middleName - self.nickname = contact.nickname - self.organization = contact.organizationName - - // email - self.emailAddresses = contact.emailAddresses.compactMap { emailNormalizer($0.value as String) } - - // phone - self.rawPhoneNumbers = contact.phoneNumbers.map(\.value.stringValue) - - // normalize phone - self.phoneNumbers = rawPhoneNumbers.compactMap { phoneNumberNormalizer($0) } - - // ignore contacts with no email nor phones - guard !emailAddresses.isEmpty || !phoneNumbers.isEmpty else { - return nil - } - } -} diff --git a/wire-ios-sync-engine/Source/Services/SupportedProtocolsService.swift b/wire-ios-sync-engine/Source/Services/SupportedProtocolsService.swift index f12897a1605..780e14e471b 100644 --- a/wire-ios-sync-engine/Source/Services/SupportedProtocolsService.swift +++ b/wire-ios-sync-engine/Source/Services/SupportedProtocolsService.swift @@ -18,6 +18,7 @@ import Foundation import WireDomain +import WireLogging import WireRequestStrategy // sourcery: AutoMockable diff --git a/wire-ios-sync-engine/Source/SessionManager/APIMigration/APIMigrationManager.swift b/wire-ios-sync-engine/Source/SessionManager/APIMigration/APIMigrationManager.swift index fac81e26afb..44a279088dc 100644 --- a/wire-ios-sync-engine/Source/SessionManager/APIMigration/APIMigrationManager.swift +++ b/wire-ios-sync-engine/Source/SessionManager/APIMigration/APIMigrationManager.swift @@ -17,7 +17,7 @@ // import Foundation -import struct WireSystem.WireLogger +import WireLogging protocol APIMigration { func perform(with session: ZMUserSession, clientID: String) async throws diff --git a/wire-ios-sync-engine/Source/SessionManager/APIMigration/AccessTokenMigration.swift b/wire-ios-sync-engine/Source/SessionManager/APIMigration/AccessTokenMigration.swift index dafaf4d3845..b58e3ba55fa 100644 --- a/wire-ios-sync-engine/Source/SessionManager/APIMigration/AccessTokenMigration.swift +++ b/wire-ios-sync-engine/Source/SessionManager/APIMigration/AccessTokenMigration.swift @@ -17,7 +17,7 @@ // import Foundation -import struct WireSystem.WireLogger +import WireLogging protocol AccessTokenRenewalObserver { func accessTokenRenewalDidSucceed() diff --git a/wire-ios-sync-engine/Source/SessionManager/APIVersionResolver.swift b/wire-ios-sync-engine/Source/SessionManager/APIVersionResolver.swift index 225307b03f1..e4f8a3a56cf 100644 --- a/wire-ios-sync-engine/Source/SessionManager/APIVersionResolver.swift +++ b/wire-ios-sync-engine/Source/SessionManager/APIVersionResolver.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireTransport final class APIVersionResolver { diff --git a/wire-ios-sync-engine/Source/SessionManager/AVSLogObserver.swift b/wire-ios-sync-engine/Source/SessionManager/AVSLogObserver.swift index fdbe14a7fdb..e7abc7fade8 100644 --- a/wire-ios-sync-engine/Source/SessionManager/AVSLogObserver.swift +++ b/wire-ios-sync-engine/Source/SessionManager/AVSLogObserver.swift @@ -17,6 +17,7 @@ // import avs +import WireLogging final class AVSLogObserver: AVSLogger { private var token: Any! diff --git a/wire-ios-sync-engine/Source/SessionManager/BackendEnvironmentProvider+Reachability.swift b/wire-ios-sync-engine/Source/SessionManager/BackendEnvironmentProvider+Reachability.swift index 54578ba5781..106d714860f 100644 --- a/wire-ios-sync-engine/Source/SessionManager/BackendEnvironmentProvider+Reachability.swift +++ b/wire-ios-sync-engine/Source/SessionManager/BackendEnvironmentProvider+Reachability.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public typealias Reachability = ReachabilityProvider & TearDownCapable diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionFactories.swift b/wire-ios-sync-engine/Source/SessionManager/SessionFactories.swift index fbe5a0227b0..0c5e9b6ba25 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionFactories.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionFactories.swift @@ -17,6 +17,7 @@ // import avs +import WireAPI import WireDataModel open class AuthenticatedSessionFactory { @@ -26,7 +27,7 @@ open class AuthenticatedSessionFactory { let flowManager: FlowManagerType let application: ZMApplication - var environment: BackendEnvironmentProvider + var environment: WireTransport.BackendEnvironment var reachability: Reachability let minTLSVersion: String? @@ -36,7 +37,7 @@ open class AuthenticatedSessionFactory { application: ZMApplication, mediaManager: MediaManagerType, flowManager: FlowManagerType, - environment: BackendEnvironmentProvider, + environment: WireTransport.BackendEnvironment, proxyUsername: String?, proxyPassword: String?, reachability: Reachability, @@ -60,6 +61,41 @@ open class AuthenticatedSessionFactory { sharedUserDefaults: UserDefaults, isDeveloperModeEnabled: Bool ) -> ZMUserSession? { + + let apiServiceFactory: APIServiceFactory = { [weak self, environment, minTLSVersion] clientID, userID in + let wireAssembly = WireAPI.Assembly( + userID: userID, + clientID: clientID, + backendEnvironment: BackendEnvironment( + url: environment.backendURL, + webSocketURL: environment.backendWSURL, + pinnedKeys: environment.trustData.map { trustData in + PinnedKey( + key: trustData.certificateKey, + hosts: trustData.hosts.map { host in + switch host.rule { + case .equals: + .equals(host.value) + case .endsWith: + .endsWith(host.value) + } + } + ) + }, + proxySettings: self?.proxySettings + ), + minTLSVersion: WireAPI.TLSVersion.minVersionFrom(minTLSVersion), + cookieEncryptionKey: UserDefaults.cookiesKey() + ) + + let authenticationManager = wireAssembly.authenticationManager + let networkService = wireAssembly.apiNetworkService + + return APIService( + networkService: networkService, + authenticationManager: authenticationManager + ) + } let transportSession = ZMTransportSession( environment: environment, proxyUsername: proxyUsername, @@ -74,6 +110,7 @@ open class AuthenticatedSessionFactory { var userSessionBuilder = ZMUserSessionBuilder() userSessionBuilder.withAllDependencies( + apiServiceFactory: apiServiceFactory, appVersion: appVersion, application: application, cryptoboxMigrationManager: CryptoboxMigrationManager(), @@ -114,6 +151,21 @@ open class AuthenticatedSessionFactory { private(set) var proxyUsername: String? private(set) var proxyPassword: String? + + private var proxySettings: ProxySettings? { + guard let proxy = environment.proxy else { return nil } + + if proxy.needsAuthentication { + guard let proxyUsername, let proxyPassword else { + fatalInternal("Proxy needs authentication but credentials are missing") + return nil + } + + return .authenticated(host: proxy.host, port: proxy.port, username: proxyUsername, password: proxyPassword) + } else { + return .unauthenticated(host: proxy.host, port: proxy.port) + } + } } // MARK: - diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift index 6e0ea571fdb..2c937b8d4e1 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift @@ -20,6 +20,7 @@ import Foundation import WireAnalytics import WireCryptobox import WireDataModel +import WireLogging import WireUtilities import ZipArchive diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+CallKitManagerDelegate.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+CallKitManagerDelegate.swift index a65962c610b..886ef7e2800 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+CallKitManagerDelegate.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+CallKitManagerDelegate.swift @@ -18,6 +18,7 @@ import CallKit import Foundation +import WireLogging import WireSystem enum ConversationLookupError: Error { diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+EncryptionAtRest.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+EncryptionAtRest.swift index 7dcf96d9a8c..429cc65649d 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+EncryptionAtRest.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+EncryptionAtRest.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging extension SessionManager: UserSessionEncryptionAtRestDelegate { diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+UserSessionLogoutDelegate.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+UserSessionLogoutDelegate.swift index 2bbc7759a8f..35df14f4050 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+UserSessionLogoutDelegate.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+UserSessionLogoutDelegate.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging protocol UserSessionLogoutDelegate: AnyObject { diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+UserSessionSelfUserClientDelegate.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+UserSessionSelfUserClientDelegate.swift index a5b0702ff12..e933f24920a 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+UserSessionSelfUserClientDelegate.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+UserSessionSelfUserClientDelegate.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging protocol UserSessionSelfUserClientDelegate: AnyObject { /// Invoked when a client is successfully registered diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+VoIPPushManagerDelegate.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+VoIPPushManagerDelegate.swift index 05e2ab8d326..eb76892c8de 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+VoIPPushManagerDelegate.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+VoIPPushManagerDelegate.swift @@ -18,6 +18,7 @@ import Foundation import PushKit +import WireLogging extension SessionManager: VoIPPushManagerDelegate { diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift index 6706dcde121..958e26cc9b9 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift @@ -24,6 +24,7 @@ import UserNotifications import WireAnalytics import WireDataModel import WireFoundation +import WireLogging import WireRequestStrategy import WireTransport import WireUtilities @@ -281,7 +282,7 @@ public final class SessionManager: NSObject, SessionManagerType { private(set) var reachability: ReachabilityWrapper - public internal(set) var environment: BackendEnvironmentProvider { + public internal(set) var environment: BackendEnvironment { didSet { reachability.tearDown() reachability = environment.reachabilityWrapper() @@ -352,7 +353,7 @@ public final class SessionManager: NSObject, SessionManagerType { delegate: SessionManagerDelegate?, application: ZMApplication, dispatchGroup: ZMSDispatchGroup? = nil, - environment: BackendEnvironmentProvider, + environment: BackendEnvironment, configuration: SessionManagerConfiguration = SessionManagerConfiguration(), detector: JailbreakDetectorProtocol = JailbreakDetector(), requiredPushTokenType: PushToken.TokenType, @@ -363,7 +364,8 @@ public final class SessionManager: NSObject, SessionManagerType { sharedUserDefaults: UserDefaults, minTLSVersion: String?, deleteUserLogs: @escaping () -> Void, - analyticsServiceConfiguration: AnalyticsServiceConfiguration? + analyticsServiceConfiguration: AnalyticsServiceConfiguration?, + countlyProvider: @escaping () -> CountlyProtocol ) { let flowManager = FlowManager(mediaManager: mediaManager) let reachability = environment.reachabilityWrapper() @@ -418,7 +420,8 @@ public final class SessionManager: NSObject, SessionManagerType { sharedUserDefaults: sharedUserDefaults, minTLSVersion: minTLSVersion, deleteUserLogs: deleteUserLogs, - analyticsServiceConfiguration: analyticsServiceConfiguration + analyticsServiceConfiguration: analyticsServiceConfiguration, + countlyProvider: countlyProvider ) configureBlacklistDownload() @@ -469,7 +472,7 @@ public final class SessionManager: NSObject, SessionManagerType { application: ZMApplication, pushRegistry: PushRegistry, dispatchGroup: ZMSDispatchGroup, - environment: BackendEnvironmentProvider, + environment: BackendEnvironment, configuration: SessionManagerConfiguration = SessionManagerConfiguration(), detector: JailbreakDetectorProtocol = JailbreakDetector(), requiredPushTokenType: PushToken.TokenType, @@ -481,7 +484,8 @@ public final class SessionManager: NSObject, SessionManagerType { sharedUserDefaults: UserDefaults, minTLSVersion: String? = nil, deleteUserLogs: (() -> Void)? = nil, - analyticsServiceConfiguration: AnalyticsServiceConfiguration? + analyticsServiceConfiguration: AnalyticsServiceConfiguration?, + countlyProvider: @escaping () -> CountlyProtocol ) { SessionManager.enableLogsByEnvironmentVariable() self.environment = environment @@ -547,7 +551,9 @@ public final class SessionManager: NSObject, SessionManagerType { self.analyticsService = AnalyticsService( config: analyticsConfig, - logger: { WireLogger.analytics.debug($0) } + deviceModel: UIDevice.current.model, + deviceOS: UIDevice.current.systemVersion, + countlyProvider: countlyProvider ) if analyticsServiceConfiguration?.didUserGiveTrackingConsent == true { @@ -1029,18 +1035,21 @@ public final class SessionManager: NSObject, SessionManagerType { with: coreDataStack ) - triggerSlowSyncIfNeeded(with: userSession) + triggerMigrationsNeedsActionsIfNeeded(with: userSession) onCompletion(userSession) } ) } - private func triggerSlowSyncIfNeeded(with userSession: ZMUserSession) { + /// Executes post migration slow sync or sync resources + private func triggerMigrationsNeedsActionsIfNeeded(with userSession: ZMUserSession) { let context = userSession.syncContext context.perform { - if context.readAndResetSlowSyncFlag() { + if context.readMigrationNeedsSlowSyncFlag() { userSession.syncStatus.forceSlowSync() + } else if context.readMigrationNeedsSyncResourcesFlag() { + userSession.syncStatus.resyncResources() } } } diff --git a/wire-ios-sync-engine/Source/Synchronization/EventProcessor.swift b/wire-ios-sync-engine/Source/Synchronization/EventProcessor.swift index c65c9e0153b..b6e41b20a77 100644 --- a/wire-ios-sync-engine/Source/Synchronization/EventProcessor.swift +++ b/wire-ios-sync-engine/Source/Synchronization/EventProcessor.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireRequestStrategy import WireUtilities diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/Asset Deletion/AssetDeletionRequestStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/Asset Deletion/AssetDeletionRequestStrategy.swift index 31ce76bdd1d..53b95269c01 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/Asset Deletion/AssetDeletionRequestStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/Asset Deletion/AssetDeletionRequestStrategy.swift @@ -31,7 +31,7 @@ private extension AssetRequestFactory { switch apiVersion { case .v0, .v1: path = "/assets/v3/\(identifier)" - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: guard let domain = BackendInfo.domain else { return nil } diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/CallingRequestStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/CallingRequestStrategy.swift index b4529760be0..8f25a951310 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/CallingRequestStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/CallingRequestStrategy.swift @@ -19,6 +19,7 @@ import Combine import Foundation import WireDataModel +import WireLogging import WireRequestStrategy @objcMembers @@ -29,8 +30,6 @@ public final class CallingRequestStrategy: AbstractRequestStrategy, ZMSingleRequ private static let logger = Logger(subsystem: "VoIP Push", category: "CallingRequestStrategy") - private let zmLog = ZMSLog(tag: "calling") - private let messageSender: MessageSenderInterface private let flowManager: FlowManagerType private let decoder = JSONDecoder() @@ -85,7 +84,6 @@ public final class CallingRequestStrategy: AbstractRequestStrategy, ZMSingleRequ let selfUser = ZMUser.selfUser(in: managedObjectContext) if let clientId = selfUser.selfClient()?.remoteIdentifier { - zmLog.debug("Creating callCenter from init") self.callCenter = WireCallCenterV3Factory.callCenter( withUserId: selfUser.avsIdentifier, clientId: clientId, @@ -125,8 +123,6 @@ public final class CallingRequestStrategy: AbstractRequestStrategy, ZMSingleRequ public func request(for sync: ZMSingleRequestSync, apiVersion: APIVersion) -> ZMTransportRequest? { switch sync { case callConfigRequestSync: - zmLog.debug("Scheduling request to '/calls/config/v2'") - return ZMTransportRequest( path: "/calls/config/v2", method: .get, @@ -145,8 +141,6 @@ public final class CallingRequestStrategy: AbstractRequestStrategy, ZMSingleRequ return nil } - zmLog.debug("Scheduling request to discover clients") - let factory = ClientMessageRequestFactory() return factory.upstreamRequestForFetchingClients( @@ -165,7 +159,6 @@ public final class CallingRequestStrategy: AbstractRequestStrategy, ZMSingleRequ public func didReceive(_ response: ZMTransportResponse, forSingleRequest sync: ZMSingleRequestSync) { switch sync { case callConfigRequestSync: - zmLog.debug("Received call config response for \(self): \(response)") if response.httpStatus == 200 { var payloadAsString: String? if let payload = response.payload, let data = try? JSONSerialization.data( @@ -174,20 +167,17 @@ public final class CallingRequestStrategy: AbstractRequestStrategy, ZMSingleRequ ) { payloadAsString = String(decoding: data, as: UTF8.self) } - zmLog.debug("Callback: \(String(describing: callConfigCompletion))") callConfigCompletion?(payloadAsString, response.httpStatus) callConfigCompletion = nil } case clientDiscoverySync: - zmLog.debug("Received client discovery response for \(self): \(response)") - defer { clientDiscoveryRequest = nil } guard response.httpStatus == 412 else { - zmLog.warn("Expected 412 response: missing clients") + Self.logger.warning("Expected 412 response: missing clients") return } @@ -200,7 +190,7 @@ public final class CallingRequestStrategy: AbstractRequestStrategy, ZMSingleRequ let payload = try decoder.decode(ClientDiscoveryResponsePayload.self, from: jsonData) clientDiscoveryRequest?.completion(payload.clients) } catch { - zmLog.error("Could not parse client discovery response: \(error.localizedDescription)") + Self.logger.error("Could not parse client discovery response: \(error.localizedDescription)") } default: @@ -228,7 +218,6 @@ public final class CallingRequestStrategy: AbstractRequestStrategy, ZMSingleRequ for object in objects { if let userClient = object as? UserClient, userClient.isSelfClient(), let clientId = userClient.remoteIdentifier, let userId = userClient.user?.avsIdentifier { - zmLog.debug("Creating callCenter") let uiContext = managedObjectContext.zm_userInterface! uiContext.performGroupedBlock { self.callCenter = WireCallCenterV3Factory.callCenter( @@ -265,15 +254,12 @@ public final class CallingRequestStrategy: AbstractRequestStrategy, ZMSingleRequ let conversationUUID = event.conversationUUID, let eventTimestamp = event.timestamp else { - zmLog.error("ignoring calling message: \(genericMessage)") + Self.logger.error("ignoring calling message: \(genericMessage)") return } - zmLog.debug("received calling message, timestamp \(eventTimestamp), serverTimeDelta \(serverTimeDelta)") - guard !callEventContent.isRemoteMute else { callCenter?.isMuted = true - zmLog.debug("muted remotely from calling message") return } @@ -328,7 +314,6 @@ public final class CallingRequestStrategy: AbstractRequestStrategy, ZMSingleRequ callEventStatus.scheduledCallEventForProcessing() callCenter?.processCallEvent(callEvent, completionHandler: { [weak self] in - self?.zmLog.debug("processed calling message") self?.callEventStatus.finishedProcessingCallEvent() }) } @@ -355,15 +340,13 @@ extension CallingRequestStrategy: WireCallCenterTransport { domain: conversationId.domain, in: self.managedObjectContext ) else { - self.zmLog.error("Not sending calling messsage since conversation doesn't exist") + Self.logger.error("Not sending calling messsage since conversation doesn't exist") completionHandler(500) return } let genericMessage = GenericMessage(content: callingContent) - self.zmLog.debug("schedule calling message") - let recipients = targets .map { self.recipients(for: $0, in: self.managedObjectContext) } ?? .conversationParticipants @@ -436,9 +419,7 @@ extension CallingRequestStrategy: WireCallCenterTransport { } public func requestCallConfig(completionHandler: @escaping CallConfigRequestCompletion) { - zmLog.debug("requestCallConfig() called, moc = \(managedObjectContext)") managedObjectContext.performGroupedBlock { [unowned self] in - zmLog.debug("requestCallConfig() on the moc queue") callConfigCompletion = completionHandler callConfigRequestSync.readyForNextRequestIfNotBusy() @@ -447,15 +428,13 @@ extension CallingRequestStrategy: WireCallCenterTransport { } public func requestClientsList(conversationId: AVSIdentifier, completionHandler: @escaping ([AVSClient]) -> Void) { - zmLog.debug("requestClientList() called, moc = \(managedObjectContext)") - managedObjectContext.performGroupedBlock { [unowned self] in guard let conversation = ZMConversation.fetch( with: conversationId.identifier, domain: conversationId.domain, in: managedObjectContext ) else { - zmLog.error("Can't request client list since conversation doesn't exist") + Self.logger.error("Can't request client list since conversation doesn't exist") completionHandler([]) return } @@ -617,7 +596,7 @@ extension CallingRequestStrategy { case .v0: // `nestedContainer` contains all the user ids with no notion of domain, we can extract clients directly allClients = try extractClientsFromContainer(nestedContainer, nil) - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: // `nestedContainer` has further nested containers each dynamically keyed by a domain name. // we need to loop over each container to extract the clients. try nestedContainer.allKeys.forEach { domainKey in diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/EvaluateOneOnOneConversationsStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/EvaluateOneOnOneConversationsStrategy.swift index e498f0679c9..8c4cd51fab7 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/EvaluateOneOnOneConversationsStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/EvaluateOneOnOneConversationsStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireRequestStrategy final class EvaluateOneOnOneConversationsStrategy: AbstractRequestStrategy { @@ -63,7 +64,11 @@ final class EvaluateOneOnOneConversationsStrategy: AbstractRequestStrategy { do { let mlsService = await syncContext.perform { syncContext.mlsService } let migrator = mlsService.map(OneOnOneMigrator.init(mlsService:)) - let resolver = OneOnOneResolver(migrator: migrator) + let mlsFeature = await FeatureRepository(context: syncContext).fetchMLS() + let resolver = OneOnOneResolver( + migrator: migrator, + isMLSEnabled: mlsFeature.isEnabled + ) try await resolver.resolveAllOneOnOneConversations(in: syncContext) await syncContext.perform { diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/LabelDownstreamRequestStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/LabelDownstreamRequestStrategy.swift index 357e45185a7..93fe34622fc 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/LabelDownstreamRequestStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/LabelDownstreamRequestStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging struct LabelUpdate: Codable, Equatable { let id: UUID diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/LegalHoldRequestStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/LegalHoldRequestStrategy.swift index 9200b723b67..f34b57490d1 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/LegalHoldRequestStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/LegalHoldRequestStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public class LegalHoldRequestStrategy: AbstractRequestStrategy, ZMSingleRequestTranscoder, ZMEventConsumer { diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/RegistationCredentialVerificationStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/RegistationCredentialVerificationStrategy.swift index 28eaee60f75..1737389956e 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/RegistationCredentialVerificationStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/RegistationCredentialVerificationStrategy.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging final class RegistationCredentialVerificationStrategy: NSObject { let registrationStatus: RegistrationStatusProtocol @@ -81,8 +82,14 @@ extension RegistationCredentialVerificationStrategy: ZMSingleRequestTranscoder { error = NSError.invalidActivationCode(with: response) ?? NSError(userSessionErrorCode: .unknownError, userInfo: [:]) default: + // We can end up here because more than one request can be sent for a single action/phase. + // This is an issue in some other part of SyncEngine but as a quick fix we will log and abort here. let phaseString = registrationStatus.phase.map { "\($0)" } ?? "" - fatal("Error occurs for invalid phase: \(phaseString)") + WireLogger.authentication.error( + "Recieved unsuccessful response for invalid phase (\(phaseString))", + attributes: .safePublic + ) + return assertionFailure("Error occurs for invalid phase: \(phaseString)") } registrationStatus.handleError(error) } diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/SearchUserImageStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/SearchUserImageStrategy.swift index c6fa2ca0f73..4594aba2fee 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/SearchUserImageStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/SearchUserImageStrategy.swift @@ -175,7 +175,7 @@ final class SearchUserImageStrategy: AbstractRequestStrategy { path = "/assets/v4/\(domain)/\(key)" - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: let domain = requestedUserDomain[user]?.isEmpty == false ? requestedUserDomain[user]! : BackendInfo .domain guard let domain else { return nil } diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/SelfSupportedProtocolsRequestStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/SelfSupportedProtocolsRequestStrategy.swift index 3cbe001b612..8f5f0fe4d50 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/SelfSupportedProtocolsRequestStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/SelfSupportedProtocolsRequestStrategy.swift @@ -18,6 +18,7 @@ import Foundation import WireDomain +import WireLogging import WireRequestStrategy public final class SelfSupportedProtocolsRequestStrategy: AbstractRequestStrategy, ZMSingleRequestTranscoder { diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/TeamDownloadRequestStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/TeamDownloadRequestStrategy.swift index 41b55a2e9b6..324460fc5f1 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/TeamDownloadRequestStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/TeamDownloadRequestStrategy.swift @@ -207,7 +207,7 @@ public final class TeamDownloadRequestStrategy: AbstractRequestStrategy, ZMConte switch apiVersion { case .v0, .v1, .v2, .v3: return TeamDownloadRequestFactory.getTeamsRequest(apiVersion: apiVersion) - case .v4, .v5, .v6: + case .v4, .v5, .v6, .v7: guard let teamID = ZMUser.selfUser(in: managedObjectContext).teamIdentifier else { syncStatus.finishCurrentSyncPhase(phase: expectedSyncPhase) return nil @@ -233,7 +233,7 @@ public final class TeamDownloadRequestStrategy: AbstractRequestStrategy, ZMConte syncStatus.finishCurrentSyncPhase(phase: expectedSyncPhase) - case .v4, .v5, .v6: + case .v4, .v5, .v6, .v7: guard let rawData = response.rawData, let teamPayload = TeamPayload(rawData) diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/TeamImageAssetUpdateStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/TeamImageAssetUpdateStrategy.swift index 1934dfcf151..6957ef62dd7 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/TeamImageAssetUpdateStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/TeamImageAssetUpdateStrategy.swift @@ -83,7 +83,7 @@ public final class TeamImageAssetUpdateStrategy: AbstractRequestStrategy, ZMCont switch apiVersion { case .v0, .v1: path = "/assets/v3/\(assetId)" - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: guard let domain = BackendInfo.domain else { return nil } path = "/assets/\(domain)/\(assetId)" } diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/TypingStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/TypingStrategy.swift index 0d2f66c821b..9eb6fc8dcf9 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/TypingStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/TypingStrategy.swift @@ -223,7 +223,7 @@ public class TypingStrategy: AbstractRequestStrategy, TearDownCapable, ZMEventCo case .v0, .v1, .v2: path = "/conversations/\(remoteIdentifier.transportString())/typing" - case .v3, .v4, .v5, .v6: + case .v3, .v4, .v5, .v6, .v7: let domain = if let domain = conversation.domain, !domain.isEmpty { domain } else { BackendInfo.domain } guard let domain else { return nil } path = "/conversations/\(domain)/\(remoteIdentifier.transportString())/typing" diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientEventConsumer.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientEventConsumer.swift index edcdfa1ae03..a6458a2e5bc 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientEventConsumer.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientEventConsumer.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging /// Consumes self user client update events /// diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestFactory.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestFactory.swift index e31cfdb5e0a..6846ad8c3fe 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestFactory.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestFactory.swift @@ -69,7 +69,7 @@ extension UserClientRequestFactory { let request = ZMTransportRequest( path: "/clients", - method: ZMTransportRequestMethod.post, + method: .post, payload: payload as ZMTransportData, apiVersion: apiVersion.rawValue ) @@ -149,7 +149,7 @@ extension UserClientRequestFactory { ] let request = ZMTransportRequest( path: "/clients/\(remoteIdentifier)", - method: ZMTransportRequestMethod.put, + method: .put, payload: payload as ZMTransportData, apiVersion: apiVersion.rawValue ) @@ -171,7 +171,7 @@ extension UserClientRequestFactory { ] let request = ZMTransportRequest( path: "/clients/\(remoteIdentifier)", - method: ZMTransportRequestMethod.put, + method: .put, payload: payload as ZMTransportData, apiVersion: apiVersion.rawValue ) @@ -224,7 +224,7 @@ extension UserClientRequestFactory { ] let request = ZMTransportRequest( path: "/clients/\(remoteIdentifier)", - method: ZMTransportRequestMethod.put, + method: .put, payload: payload as ZMTransportData, apiVersion: apiVersion.rawValue ) @@ -257,7 +257,7 @@ extension UserClientRequestFactory { let request = ZMTransportRequest( path: "/clients/\(client.remoteIdentifier!)", - method: ZMTransportRequestMethod.delete, + method: .delete, payload: payload as ZMTransportData, apiVersion: apiVersion.rawValue ) diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestStrategy.swift index 138f0e40939..cba96ea3696 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserClientRequestStrategy.swift @@ -19,6 +19,7 @@ import Foundation import WireCryptobox import WireDataModel +import WireLogging import WireSystem import WireTransport import WireUtilities diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserImageAssetUpdateStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserImageAssetUpdateStrategy.swift index f13af42f495..9155683a726 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserImageAssetUpdateStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserImageAssetUpdateStrategy.swift @@ -203,7 +203,7 @@ public final class UserImageAssetUpdateStrategy: AbstractRequestStrategy, ZMCont path = "/assets/v4/\(domain)/\(assetId)" - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: let domain = if let domain = user.domain, !domain.isEmpty { domain } else { BackendInfo.domain } guard let domain else { return nil } diff --git a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserProfileUpdateRequestStrategy.swift b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserProfileUpdateRequestStrategy.swift index 99b60635f4e..01683d19ce8 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Strategies/UserProfileUpdateRequestStrategy.swift +++ b/wire-ios-sync-engine/Source/Synchronization/Strategies/UserProfileUpdateRequestStrategy.swift @@ -132,8 +132,11 @@ public class UserProfileUpdateRequestStrategy: AbstractRequestStrategy, ZMSingle case handleCheckSync: let handle = userProfileUpdateStatus.handleToCheck! + let path = apiVersion >= .v7 + ? "/handles/\(handle)" + : "/users/handles/\(handle)" return ZMTransportRequest( - path: "/users/handles/\(handle)", + path: path, method: .head, payload: nil, apiVersion: apiVersion.rawValue @@ -152,12 +155,15 @@ public class UserProfileUpdateRequestStrategy: AbstractRequestStrategy, ZMSingle guard let handlesToCheck = userProfileUpdateStatus.suggestedHandlesToCheck else { fatal("Tried to check handles availability, but no handle was available") } + let path = apiVersion >= .v7 + ? "/handles" + : "/users/handles" let payload = [ "handles": handlesToCheck, "return": 1 ] as NSDictionary return ZMTransportRequest( - path: "/users/handles", + path: path, method: .post, payload: payload, apiVersion: apiVersion.rawValue diff --git a/wire-ios-sync-engine/Source/Synchronization/StrategyDirectory.swift b/wire-ios-sync-engine/Source/Synchronization/StrategyDirectory.swift index adecd1a915d..83273884dd6 100644 --- a/wire-ios-sync-engine/Source/Synchronization/StrategyDirectory.swift +++ b/wire-ios-sync-engine/Source/Synchronization/StrategyDirectory.swift @@ -137,7 +137,11 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol { quickSyncObserver: quickSyncObserver, context: syncMOC ) - let oneOnOneResolver = OneOnOneResolver(migrator: OneOnOneMigrator(mlsService: mlsService)) + let mlsFeature = FeatureRepository(context: syncMOC).fetchMLS() + let oneOnOneResolver = OneOnOneResolver( + migrator: OneOnOneMigrator(mlsService: mlsService), + isMLSEnabled: mlsFeature.isEnabled + ) return [ @@ -355,6 +359,11 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol { applicationStatus: applicationStatusDirectory, syncProgress: applicationStatusDirectory.syncStatus ), + FetchBackendMLSPublicKeysRequestStrategy( + withManagedObjectContext: syncMOC, + applicationStatus: applicationStatusDirectory, + syncProgress: applicationStatusDirectory.syncStatus + ), TerminateFederationRequestStrategy( withManagedObjectContext: syncMOC, applicationStatus: applicationStatusDirectory diff --git a/wire-ios-sync-engine/Source/Synchronization/Transcoders/ZMMissingUpdateEventsTranscoder.m b/wire-ios-sync-engine/Source/Synchronization/Transcoders/ZMMissingUpdateEventsTranscoder.m index bbb87ea6324..00f361cd299 100644 --- a/wire-ios-sync-engine/Source/Synchronization/Transcoders/ZMMissingUpdateEventsTranscoder.m +++ b/wire-ios-sync-engine/Source/Synchronization/Transcoders/ZMMissingUpdateEventsTranscoder.m @@ -175,7 +175,7 @@ - (NSUUID *)processUpdateEventsAndReturnLastNotificationIDFromResponse:(ZMTransp if (!event.isTransient) { latestEventId = event.uuid; } - [WireLoggerObjc logReceivedUpdateEventWithId:[event safeUUID]]; + [WireLoggerObjC logReceivedUpdateEventWithId:[event safeUUID]]; } } diff --git a/wire-ios-sync-engine/Source/Synchronization/ZMOperationLoop+PushChannel.swift b/wire-ios-sync-engine/Source/Synchronization/ZMOperationLoop+PushChannel.swift index a100ef3a283..842ff8ef8f0 100644 --- a/wire-ios-sync-engine/Source/Synchronization/ZMOperationLoop+PushChannel.swift +++ b/wire-ios-sync-engine/Source/Synchronization/ZMOperationLoop+PushChannel.swift @@ -18,6 +18,7 @@ import Foundation import WireAPI +import WireLogging extension ZMOperationLoop: ZMPushChannelConsumer { @@ -31,7 +32,7 @@ extension ZMOperationLoop: ZMPushChannelConsumer { // fix it. Once we're sure it works, we should remove this. do { let decoder = JSONDecoder.defaultDecoder - _ = try decoder.decode(UpdateEventEnvelope.self, from: data) + _ = try decoder.decode(UpdateEventEnvelopeV0.self, from: data) } catch { WireLogger.updateEvent.error("failed to decode 'UpdateEventEnvelope': \(error)") } diff --git a/wire-ios-sync-engine/Source/Use cases/CheckOneOnOneConversationIsReadyUseCase.swift b/wire-ios-sync-engine/Source/Use cases/CheckOneOnOneConversationIsReadyUseCase.swift index 4ec95fbec8f..c8b28036b4f 100644 --- a/wire-ios-sync-engine/Source/Use cases/CheckOneOnOneConversationIsReadyUseCase.swift +++ b/wire-ios-sync-engine/Source/Use cases/CheckOneOnOneConversationIsReadyUseCase.swift @@ -89,7 +89,7 @@ struct CheckOneOnOneConversationIsReadyUseCase: CheckOneOnOneConversationIsReady private func isMLSConversationEstablished(groupID: MLSGroupID) async throws -> Bool { try await coreCryptoProvider.coreCrypto().perform { - await $0.conversationExists(conversationId: groupID.data) + try await $0.conversationExists(conversationId: groupID.data) } } diff --git a/wire-ios-sync-engine/Source/Use cases/DisableAnalyticsUseCase.swift b/wire-ios-sync-engine/Source/Use cases/DisableAnalyticsUseCase.swift index 6c9d4ce9a29..8bb9ec297db 100644 --- a/wire-ios-sync-engine/Source/Use cases/DisableAnalyticsUseCase.swift +++ b/wire-ios-sync-engine/Source/Use cases/DisableAnalyticsUseCase.swift @@ -16,7 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Countly import WireAnalytics // sourcery: AutoMockable diff --git a/wire-ios-sync-engine/Source/Use cases/E2EIdentityCertificateUpdateStatusUseCase.swift b/wire-ios-sync-engine/Source/Use cases/E2EIdentityCertificateUpdateStatusUseCase.swift index d59ec5c426b..91ac26e139c 100644 --- a/wire-ios-sync-engine/Source/Use cases/E2EIdentityCertificateUpdateStatusUseCase.swift +++ b/wire-ios-sync-engine/Source/Use cases/E2EIdentityCertificateUpdateStatusUseCase.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public enum E2EIdentityCertificateUpdateStatus { diff --git a/wire-ios-sync-engine/Source/Use cases/EnableAnalyticsUseCase.swift b/wire-ios-sync-engine/Source/Use cases/EnableAnalyticsUseCase.swift index 2570f07ffe6..0ef32fa9496 100644 --- a/wire-ios-sync-engine/Source/Use cases/EnableAnalyticsUseCase.swift +++ b/wire-ios-sync-engine/Source/Use cases/EnableAnalyticsUseCase.swift @@ -16,7 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Countly import Foundation import WireAnalytics diff --git a/wire-ios-sync-engine/Source/Use cases/GetUserClientFingerprintUseCase.swift b/wire-ios-sync-engine/Source/Use cases/GetUserClientFingerprintUseCase.swift index c816166a556..e16835a356a 100644 --- a/wire-ios-sync-engine/Source/Use cases/GetUserClientFingerprintUseCase.swift +++ b/wire-ios-sync-engine/Source/Use cases/GetUserClientFingerprintUseCase.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireRequestStrategy // sourcery: AutoMockable diff --git a/wire-ios-sync-engine/Source/Use cases/SearchUsersUseCase.swift b/wire-ios-sync-engine/Source/Use cases/SearchUsersUseCase.swift new file mode 100644 index 00000000000..fccfa2f351d --- /dev/null +++ b/wire-ios-sync-engine/Source/Use cases/SearchUsersUseCase.swift @@ -0,0 +1,109 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation +import WireUtilities + +// sourcery: AutoMockable +public protocol SearchUsersUseCaseProtocol { + func invoke( + query: String, + options: SearchOptions, + messageProtocol: MessageProtocol? + ) async throws -> SearchResult +} + +public class SearchUsersUseCase: SearchUsersUseCaseProtocol { + + // MARK: - Properties + + private let context: NSManagedObjectContext + private let searchDirectory: SearchDirectory + private let isFederationUsageAllowed: Bool + private var activeSearchTask: SearchTask? + + deinit { + searchDirectory.tearDown() + } + + // MARK: - Initialization + + init( + context: NSManagedObjectContext, + searchDirectory: SearchDirectory, + isFederationUsageAllowed: Bool + ) { + self.context = context + self.searchDirectory = searchDirectory + self.isFederationUsageAllowed = isFederationUsageAllowed + } + + // MARK: - Public Interface + + public func invoke( + query: String, + options: SearchOptions, + messageProtocol: MessageProtocol? + ) async throws -> SearchResult { + activeSearchTask?.cancel() + activeSearchTask = nil + + searchDirectory.updateIncompleteMetadataIfNeeded() + + let (selfDomain, team) = await context.perform { + let selfUser = ZMUser.selfUser(in: self.context) + return (selfUser.domain, selfUser.membership?.team) + } + + let searchDomain = isOtherDomainSearchAllowed(messageProtocol) ? nil : selfDomain + let request = SearchRequest( + query: query.trim(), + searchDomain: searchDomain, + searchOptions: options, + team: team + ) + + return try await withCheckedThrowingContinuation { continuation in + guard !Task.isCancelled else { + continuation.resume(throwing: CancellationError()) + self.activeSearchTask = nil + return + } + + let task = searchDirectory.perform(request) + task.addResultHandler { result, isCompleted in + if isCompleted { + continuation.resume(returning: result) + self.activeSearchTask = nil + } + } + task.start() + activeSearchTask = task + } + } + + // MARK: - Private methods + + private func isOtherDomainSearchAllowed(_ messageProtocol: MessageProtocol?) -> Bool { + guard let messageProtocol else { + return isFederationUsageAllowed + } + return BackendInfo.isMLSEnabled ? messageProtocol != .proteus : true + } + +} diff --git a/wire-ios-sync-engine/Source/Use cases/ShareFileUseCase.swift b/wire-ios-sync-engine/Source/Use cases/ShareFileUseCase.swift index e73553978fc..5d519a61d6b 100644 --- a/wire-ios-sync-engine/Source/Use cases/ShareFileUseCase.swift +++ b/wire-ios-sync-engine/Source/Use cases/ShareFileUseCase.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging // sourcery: AutoMockable public protocol ShareFileUseCaseProtocol { diff --git a/wire-ios-sync-engine/Source/UserSession/NSManagedObject+CryptoStack.swift b/wire-ios-sync-engine/Source/UserSession/NSManagedObject+CryptoStack.swift index f6e493ab3aa..c4a47475673 100644 --- a/wire-ios-sync-engine/Source/UserSession/NSManagedObject+CryptoStack.swift +++ b/wire-ios-sync-engine/Source/UserSession/NSManagedObject+CryptoStack.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging public extension NSManagedObjectContext { diff --git a/wire-ios-sync-engine/Source/UserSession/OperationStatus.swift b/wire-ios-sync-engine/Source/UserSession/OperationStatus.swift index 0342c7ab4fc..dd0a00373df 100644 --- a/wire-ios-sync-engine/Source/UserSession/OperationStatus.swift +++ b/wire-ios-sync-engine/Source/UserSession/OperationStatus.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public typealias BackgroundFetchHandler = (_ fetchResult: UIBackgroundFetchResult) -> Void diff --git a/wire-ios-sync-engine/Source/UserSession/Search/AddressBookSearch.swift b/wire-ios-sync-engine/Source/UserSession/Search/AddressBookSearch.swift deleted file mode 100644 index c8b487dae58..00000000000 --- a/wire-ios-sync-engine/Source/UserSession/Search/AddressBookSearch.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Contacts -import Foundation - -/// Search for contacts in the address book -class AddressBookSearch { - - /// Maximum number of contacts to consider when matching/searching, - /// for performance reasons - private let maximumSearchRange: UInt = 3000 - - /// Address book - private let addressBook: AddressBookAccessor? - - init(addressBook: AddressBookAccessor? = nil) { - self.addressBook = addressBook ?? AddressBook.factory() - } -} - -// MARK: - Search contacts - -extension AddressBookSearch { - - /// Returns address book contacts matching the query, excluding the one with the given identifier - func contactsMatchingQuery(_ query: String, identifiersToExclude: [String]) -> [ZMAddressBookContact] { - let excluded = Set(identifiersToExclude) - let addressBookMatches = addressBook?.contacts(matchingQuery: query.lowercased()) ?? [] - - return addressBookMatches.filter { contact in - guard let identifier = contact.localIdentifier else { - return true - } - return !excluded.contains(identifier) - } - } -} diff --git a/wire-ios-sync-engine/Source/UserSession/Search/SearchRequest.swift b/wire-ios-sync-engine/Source/UserSession/Search/SearchRequest.swift index 6680c9c6328..2d8affe2880 100644 --- a/wire-ios-sync-engine/Source/UserSession/Search/SearchRequest.swift +++ b/wire-ios-sync-engine/Source/UserSession/Search/SearchRequest.swift @@ -26,41 +26,37 @@ public struct SearchOptions: OptionSet { public static let contacts = SearchOptions(rawValue: 1 << 0) - /// Users found in your address book. - - public static let addressBook = SearchOptions(rawValue: 1 << 1) - /// Users which are a member of the same team as you. - public static let teamMembers = SearchOptions(rawValue: 1 << 2) + public static let teamMembers = SearchOptions(rawValue: 1 << 1) /// Exclude team members which aren't in an active conversation with you. - public static let excludeNonActiveTeamMembers = SearchOptions(rawValue: 1 << 3) + public static let excludeNonActiveTeamMembers = SearchOptions(rawValue: 1 << 2) /// Exclude team members with the role .partner which aren't in an active conversation with you. - public static let excludeNonActivePartners = SearchOptions(rawValue: 1 << 4) + public static let excludeNonActivePartners = SearchOptions(rawValue: 1 << 3) /// Users from the public directory. - public static let directory = SearchOptions(rawValue: 1 << 5) + public static let directory = SearchOptions(rawValue: 1 << 4) /// Group conversations you are or were a participant of. - public static let conversations = SearchOptions(rawValue: 1 << 6) + public static let conversations = SearchOptions(rawValue: 1 << 5) /// Services which are enabled in your team. - public static let services = SearchOptions(rawValue: 1 << 7) + public static let services = SearchOptions(rawValue: 1 << 6) /// Users from federated servers. - public static let federated = SearchOptions(rawValue: 1 << 8) + public static let federated = SearchOptions(rawValue: 1 << 7) /// Only search the local database. - public static let localResultsOnly = SearchOptions(rawValue: 1 << 9) + public static let localResultsOnly = SearchOptions(rawValue: 1 << 8) public init(rawValue: Int) { self.rawValue = rawValue @@ -110,10 +106,15 @@ public struct SearchRequest { let searchDomain: String? let searchOptions: SearchOptions - public init(query: String, searchOptions: SearchOptions, team: Team? = nil) { - let (query, searchDomain) = Self.parseQuery(query) + public init( + query: String, + searchDomain: String? = nil, + searchOptions: SearchOptions, + team: Team? = nil + ) { + let (query, parsedDomain) = Self.parseQuery(query) self.query = query - self.searchDomain = searchDomain + self.searchDomain = searchDomain ?? parsedDomain self.searchOptions = searchOptions self.team = team } diff --git a/wire-ios-sync-engine/Source/UserSession/Search/SearchResult+AddressBook.swift b/wire-ios-sync-engine/Source/UserSession/Search/SearchResult+AddressBook.swift deleted file mode 100644 index 815cf02a51c..00000000000 --- a/wire-ios-sync-engine/Source/UserSession/Search/SearchResult+AddressBook.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Contacts -import Foundation - -/// This is used for testing only -var debug_searchResultAddressBookOverride: AddressBookAccessor? - -extension SearchResult { - - /// Creates a new search result with the same results and additional - /// results obtained by searching through the address book with the same query - public func extendWithContactsFromAddressBook(_ query: String, contextProvider: ContextProvider) -> SearchResult { - // When I have a search result obtained (either with a local search or from the BE) by matching on Wire - // users display names or handle, I also want to check if I have any address book contact in my local - // address book that match the query. However, matching local contacts might overlap in a number of ways - // with the users that I already found from the Wire search. The following code makes sure that such overlaps - // are not displayed twice (once for the Wire user, once for the address book contact). - let addressBook = AddressBookSearch(addressBook: debug_searchResultAddressBookOverride) - - // I don't need to find the address book contacts of users that I already found - let identifiersOfAlreadyFoundUsers = contacts - .compactMap { $0.user?.addressBookEntry?.localIdentifier } + directory - .compactMap { $0.user?.addressBookEntry?.localIdentifier } - let allMatchingAddressBookContacts = addressBook.contactsMatchingQuery( - query, - identifiersToExclude: identifiersOfAlreadyFoundUsers - ) - - // There might also be contacts for which the local address book name match and which are also Wire users, but - // on Wire their name doesn't match, - // so the Wire search did not return them. If I figure out which Wire users they match, I want to include those - // users into - // the result as Wire user results, not an non-Wire address book results - - let (additionalUsersFromAddressBook, addressBookContactsWithoutUser) = contactsThatAreAlsoUsers( - contacts: allMatchingAddressBookContacts, - managedObjectContext: contextProvider.viewContext - ) - let searchUsersFromAddressBook = addressBookContactsWithoutUser.compactMap { - ZMSearchUser( - contextProvider: contextProvider, - contact: $0, - searchUsersCache: searchUsersCache - ) - } - - // of those users, which one are connected and which one are not? - let additionalConnectedUsers = additionalUsersFromAddressBook - .filter { $0.connection != nil } - .compactMap { - ZMSearchUser( - contextProvider: contextProvider, - user: $0, - searchUsersCache: searchUsersCache - ) - } - let additionalNonConnectedUsers = additionalUsersFromAddressBook - .filter { $0.connection == nil } - .compactMap { - ZMSearchUser( - contextProvider: contextProvider, - user: $0, - searchUsersCache: searchUsersCache - ) - } - - return SearchResult( - contacts: contacts, - teamMembers: teamMembers, - addressBook: contacts + additionalConnectedUsers + additionalNonConnectedUsers + searchUsersFromAddressBook, - directory: directory, - conversations: conversations, - services: services, - searchUsersCache: searchUsersCache - ) - } - - /// Returns users that are linked to the given address book contacts - private func contactsThatAreAlsoUsers( - contacts: [ZMAddressBookContact], - managedObjectContext: NSManagedObjectContext - ) -> (users: [ZMUser], nonMatchedContacts: [ZMAddressBookContact]) { - - guard !contacts.isEmpty else { - return (users: [], nonMatchedContacts: []) - } - - var identifiersToContact = [String: ZMAddressBookContact]() - contacts.forEach { - guard let identifier = $0.localIdentifier else { return } - identifiersToContact[identifier] = $0 - } - - let predicate = NSPredicate(format: "addressBookEntry.localIdentifier IN %@", Set(identifiersToContact.keys)) - let fetchRequest = ZMUser.sortedFetchRequest(with: predicate) - fetchRequest.returnsObjectsAsFaults = false - - guard let users = try! managedObjectContext.fetch(fetchRequest) as? [ZMUser] else { - fatal("fetchOrAssert failed") - } - - for user in users { - guard let localIdentifier = user.addressBookEntry?.localIdentifier else { continue } - identifiersToContact.removeValue(forKey: localIdentifier) - } - - return (users: users, nonMatchedContacts: Array(identifiersToContact.values)) - } -} diff --git a/wire-ios-sync-engine/Source/UserSession/Search/SearchResult.swift b/wire-ios-sync-engine/Source/UserSession/Search/SearchResult.swift index 054f98807bf..e8b4e42e5c3 100644 --- a/wire-ios-sync-engine/Source/UserSession/Search/SearchResult.swift +++ b/wire-ios-sync-engine/Source/UserSession/Search/SearchResult.swift @@ -23,8 +23,6 @@ public struct SearchResult { public var contacts: [ZMSearchUser] /// Users from the team. public var teamMembers: [ZMSearchUser] - /// Legacy, not used anymore. - public var addressBook: [ZMSearchUser] /// Non-connected users. public var directory: [ZMSearchUser] /// Group conversations. @@ -62,7 +60,6 @@ extension SearchResult { ) self.contacts = [] - self.addressBook = [] self.directory = searchUsers.filter { !$0.isConnected && !$0.isTeamMember } self.conversations = [] self.services = [] @@ -94,7 +91,6 @@ extension SearchResult { self.contacts = [] self.teamMembers = [] - self.addressBook = [] self.directory = [] self.conversations = [] self.services = searchUsersServices @@ -121,7 +117,6 @@ extension SearchResult { self.contacts = [] self.teamMembers = [] - self.addressBook = [] self.directory = [searchUser] self.conversations = [] self.services = [] @@ -164,7 +159,6 @@ extension SearchResult { return SearchResult( contacts: contacts, teamMembers: teamMembers, - addressBook: addressBook, directory: directory, conversations: copiedConversations, services: services, @@ -176,7 +170,6 @@ extension SearchResult { SearchResult( contacts: result.contacts, teamMembers: result.teamMembers, - addressBook: result.addressBook, directory: directory, conversations: result.conversations, services: services, @@ -188,7 +181,6 @@ extension SearchResult { SearchResult( contacts: contacts, teamMembers: teamMembers, - addressBook: addressBook, directory: directory, conversations: conversations, services: result.services, @@ -200,7 +192,6 @@ extension SearchResult { SearchResult( contacts: contacts, teamMembers: Array(Set(teamMembers).union(result.teamMembers)), - addressBook: addressBook, directory: result.directory, conversations: conversations, services: services, diff --git a/wire-ios-sync-engine/Source/UserSession/Search/SearchTask.swift b/wire-ios-sync-engine/Source/UserSession/Search/SearchTask.swift index c546d3f815d..04ab470e217 100644 --- a/wire-ios-sync-engine/Source/UserSession/Search/SearchTask.swift +++ b/wire-ios-sync-engine/Source/UserSession/Search/SearchTask.swift @@ -43,7 +43,6 @@ public class SearchTask { private var result = SearchResult( contacts: [], teamMembers: [], - addressBook: [], directory: [], conversations: [], services: [], @@ -163,7 +162,8 @@ extension SearchTask { /// search for the local user with matching user ID and active let activeMembers = teamMembers(matchingQuery: "", team: selfUser.team, searchOptions: options) let teamMembers = activeMembers.filter { $0.remoteIdentifier == userId } - let connectedUsers = connectedUsers(matchingQuery: "").filter { $0.remoteIdentifier == userId } + let connectedUsers = connectedUsers(matchingQuery: "", hostedOnDomain: nil) + .filter { $0.remoteIdentifier == userId } contextProvider.viewContext.performGroupedBlock { [self] in @@ -187,7 +187,6 @@ extension SearchTask { searchUsersCache: searchUsersCache ) }, - addressBook: [], directory: [], conversations: [], services: [], @@ -215,7 +214,10 @@ extension SearchTask { let selfUser = ZMUser.selfUser(in: searchContext) let connectedUsers = request.searchOptions - .contains(.contacts) ? connectedUsers(matchingQuery: request.normalizedQuery) : [] + .contains(.contacts) ? connectedUsers( + matchingQuery: request.normalizedQuery, + hostedOnDomain: request.searchDomain + ) : [] let teamMembers = request.searchOptions.contains(.teamMembers) ? teamMembers( matchingQuery: request.normalizedQuery, team: team, @@ -257,7 +259,6 @@ extension SearchTask { let result = SearchResult( contacts: searchConnectedUsers, teamMembers: searchTeamMembers, - addressBook: [], directory: [], conversations: conversations, services: [], @@ -266,13 +267,6 @@ extension SearchTask { self.result = self.result.union(withLocalResult: result.copy(on: contextProvider.viewContext)) - if request.searchOptions.contains(.addressBook) { - self.result = self.result.extendWithContactsFromAddressBook( - request.normalizedQuery, - contextProvider: contextProvider - ) - } - tasksRemaining -= 1 } } @@ -315,8 +309,16 @@ extension SearchTask { return result } - func connectedUsers(matchingQuery query: String) -> [ZMUser] { - let fetchRequest = ZMUser.sortedFetchRequest(with: ZMUser.predicateForConnectedUsers(withSearch: query)) + func connectedUsers(matchingQuery query: String, hostedOnDomain: String?) -> [ZMUser] { + let fetchRequest: NSFetchRequest = if let hostedOnDomain { + ZMUser.sortedFetchRequest(with: ZMUser.predicateForConnectedUsers( + withSearch: query, + hostedOnDomain: hostedOnDomain + )) + } else { + ZMUser.sortedFetchRequest(with: ZMUser.predicateForConnectedUsers(withSearch: query)) + } + return searchContext.fetchOrAssert(request: fetchRequest) as? [ZMUser] ?? [] } @@ -615,7 +617,6 @@ extension SearchTask { self?.result = SearchResult( contacts: prevResult.contacts, teamMembers: prevResult.teamMembers, - addressBook: prevResult.addressBook, directory: result.directory + prevResult.directory, conversations: prevResult.conversations, services: prevResult.services, diff --git a/wire-ios-sync-engine/Source/UserSession/SyncStatus.swift b/wire-ios-sync-engine/Source/UserSession/SyncStatus.swift index e5c151a475a..869fe47725f 100644 --- a/wire-ios-sync-engine/Source/UserSession/SyncStatus.swift +++ b/wire-ios-sync-engine/Source/UserSession/SyncStatus.swift @@ -16,6 +16,8 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import WireLogging + private let zmLog = ZMSLog(tag: "SyncStatus") public extension Notification.Name { @@ -120,16 +122,20 @@ public class SyncStatus: NSObject, SyncStatusProtocol, SyncProgress { ZMUser.selfUser(in: managedObjectContext).needsPropertiesUpdate = true // Reset the status. currentSyncPhase = SyncPhase.fetchingLastUpdateEventID + RequestAvailableNotification.notifyNewRequestsAvailable(nil) log("slow sync") syncStateDelegate?.didStartSlowSync() } /// Sync the resources: Teams, Users, Conversations... - func resyncResources() { + public func resyncResources() { // Refetch user settings. ZMUser.selfUser(in: managedObjectContext).needsPropertiesUpdate = true - // Set the status. - currentSyncPhase = SyncPhase.fetchingLastUpdateEventID.nextPhase + // If we don't have a last event id, we need to get that first, otherwise the quick sync will fetch all events + // in the notification queue. + currentSyncPhase = hasPersistedLastEventID ? SyncPhase.fetchingLastUpdateEventID + .nextPhase : .fetchingLastUpdateEventID + RequestAvailableNotification.notifyNewRequestsAvailable(nil) log("resyncResources") syncStateDelegate?.didStartSlowSync() } diff --git a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift index cbba5a2e652..a5d15938809 100644 --- a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift +++ b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift @@ -47,7 +47,6 @@ final class ConnectToBotURLActionProcessor: NSObject, URLActionProcessor { remoteIdentifier: serviceUserData.service, teamIdentifier: nil, user: nil, - contact: nil, searchUsersCache: searchUsersCache ) serviceUser.providerIdentifier = serviceUserData.provider.transportString() diff --git a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/DeepLinkURLActionProcessor.swift b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/DeepLinkURLActionProcessor.swift index c9364e237e7..f2305561165 100644 --- a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/DeepLinkURLActionProcessor.swift +++ b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/DeepLinkURLActionProcessor.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireRequestStrategy class DeepLinkURLActionProcessor: URLActionProcessor { diff --git a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ImportEventsURLActionProcessor.swift b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ImportEventsURLActionProcessor.swift index 0c81cbc4b7e..87eadf254cd 100644 --- a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ImportEventsURLActionProcessor.swift +++ b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ImportEventsURLActionProcessor.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging enum ImportEventsError: Error { case fileNotFound(String) diff --git a/wire-ios-sync-engine/Source/UserSession/UserSession.swift b/wire-ios-sync-engine/Source/UserSession/UserSession.swift index ab0b0df4a11..ee945cfa740 100644 --- a/wire-ios-sync-engine/Source/UserSession/UserSession.swift +++ b/wire-ios-sync-engine/Source/UserSession/UserSession.swift @@ -273,6 +273,8 @@ public protocol UserSession: AnyObject { func makeConversationFolderCreationUseCase() -> CreateConversationFolderUseCase + func makeSearchUsersUseCase() -> SearchUsersUseCaseProtocol + func fetchSelfConversationMLSGroupID() async -> MLSGroupID? func e2eIdentityUpdateCertificateUpdateStatus() -> E2EIdentityCertificateUpdateStatusUseCaseProtocol? diff --git a/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift b/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift index 1121bba70de..0d93c18f0bf 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging import WireSystem @objc(ZMClientRegistrationPhase) @@ -251,7 +252,7 @@ public class ZMClientRegistrationStatus: NSObject, ClientRegistrationDelegate { } public var clientIsReadyForRequests: Bool { - currentPhase == .registered && !needsToRegisterMLSCLient + currentPhase == .registered && !needsToRegisterMLSClient } var isWaitingForLogin: Bool { @@ -262,7 +263,7 @@ public class ZMClientRegistrationStatus: NSObject, ClientRegistrationDelegate { Self.needsToRegisterClient(in: managedObjectContext) } - var needsToRegisterMLSCLient: Bool { + var needsToRegisterMLSClient: Bool { Self.needsToRegisterMLSClient(in: managedObjectContext) } @@ -301,7 +302,7 @@ public class ZMClientRegistrationStatus: NSObject, ClientRegistrationDelegate { needsToFetchFeatureConfigs = needsToRegisterClient needsRefreshSelfUser = needsToRegisterClient - if !needsToRegisterClient, needsToRegisterMLSCLient { + if !needsToRegisterClient, needsToRegisterMLSClient { guard let client = ZMUser.selfUser(in: managedObjectContext).selfClient() else { fatal("Expected a self user client to exist") } @@ -455,6 +456,20 @@ public class ZMClientRegistrationStatus: NSObject, ClientRegistrationDelegate { } } + private func fetchBackendMLSPublicKeys() { + var action = FetchBackendMLSPublicKeysAction() + action.perform(in: managedObjectContext.notificationContext) { [weak self] result in + switch result { + case let .success(backendPublicKeys): + let hasValidKeys = backendPublicKeys.removal.hasValidKeys() + BackendInfo.isMLSEnabled = hasValidKeys + case .failure: + WireLogger.authentication.info("Backend doesn't have MLS public keys") + } + self?.didFetchBackendMLSPublicKeys() + } + } + @objc public func didDeleteClient() { WireLogger.userClient.info("client was deleted. will prepare for registration") @@ -479,7 +494,7 @@ public class ZMClientRegistrationStatus: NSObject, ClientRegistrationDelegate { prekeys = nil lastResortPrekey = nil - if needsToRegisterMLSCLient { + if needsToRegisterMLSClient { createMLSClient(client: client) } else { registrationStatusDelegate?.didRegisterSelfUserClient(client) @@ -604,6 +619,11 @@ public class ZMClientRegistrationStatus: NSObject, ClientRegistrationDelegate { public func didFetchFeatureConfigs() { WireLogger.userClient.info("did fetch feature configs") needsToFetchFeatureConfigs = false + fetchBackendMLSPublicKeys() + } + + public func didFetchBackendMLSPublicKeys() { + WireLogger.userClient.info("did fetch backend MLS public keys") RequestAvailableNotification.notifyNewRequestsAvailable(self) } @@ -648,14 +668,22 @@ public class ZMClientRegistrationStatus: NSObject, ClientRegistrationDelegate { FeatureRepository(context: managedObjectContext).fetchE2EI().isEnabled } + private var isMLSEnabled: Bool { + FeatureRepository(context: managedObjectContext).fetchMLS().isEnabled + } + @objc(needsToRegisterMLSClientInContext:) public static func needsToRegisterMLSClient(in context: NSManagedObjectContext) -> Bool { guard !needsToRegisterClient(in: context) else { return false } let hasRegisteredMLSClient = ZMUser.selfUser(in: context).selfClient()?.hasRegisteredMLSClient ?? false - let isAllowedToRegisterMLSCLient = DeveloperFlag.enableMLSSupport.isOn && (BackendInfo.apiVersion ?? .v0) >= .v5 - return !hasRegisteredMLSClient && isAllowedToRegisterMLSCLient + let mlsFeature = FeatureRepository(context: context).fetchMLS() + + let shouldRegisterMLSCLient = mlsFeature.isEnabled + let canRegisterMLSCLient = BackendInfo.isMLSEnabled + + return !hasRegisteredMLSClient && shouldRegisterMLSCLient && canRegisterMLSCLient } public func willGeneratePrekeys() { diff --git a/wire-ios/Wire-iOS Tests/WireAnalytics_DatadogTests.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/UserSession+Federation.swift similarity index 56% rename from wire-ios/Wire-iOS Tests/WireAnalytics_DatadogTests.swift rename to wire-ios-sync-engine/Source/UserSession/ZMUserSession/UserSession+Federation.swift index bf69cddaa01..f631bcb1262 100644 --- a/wire-ios/Wire-iOS Tests/WireAnalytics_DatadogTests.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/UserSession+Federation.swift @@ -16,27 +16,26 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import XCTest -@testable import WireCommonComponents +public extension UserSession { -class WireAnalytics_DatadogTests: XCTestCase { - - func test_enable_isExecutedOnlyOnce() { - // GIVEN - var count = 0 - WireAnalytics.Datadog.enableOnlyOnce = .init { - count += 1 + var defaultProtocol: MessageProtocol { + guard mlsFeature.isEnabled else { + return .proteus } - let concurrentQueue = DispatchQueue(label: "test", attributes: .concurrent) - - // WHEN - for i in 1 ... 1000 { - concurrentQueue.async { - WireAnalytics.Datadog.enable() - } + switch mlsFeature.config.defaultProtocol { + case .proteus, .mixed: + return .proteus + case .mls: + return .mls } + } - // THEN - XCTAssertEqual(count, 1) + var isFederationUsageAllowed: Bool { + guard BackendInfo.isMLSEnabled else { + // If there is no MLS removal key configured,federation search is allowed. + return true + } + return defaultProtocol != .proteus } + } diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+AccessToken.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+AccessToken.swift index 287b38f8f1a..81394420067 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+AccessToken.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+AccessToken.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging extension ZMUserSession: AccessTokenRenewing { diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+AnalyticsUser.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+AnalyticsUser.swift index 5465155f1d4..33cc8b131c5 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+AnalyticsUser.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+AnalyticsUser.swift @@ -19,6 +19,7 @@ import Foundation import WireAnalytics import WireDataModel +import WireLogging extension ZMUserSession: AnalyticsEventTrackerProvider { diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+CertificateRevocationLists.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+CertificateRevocationLists.swift index 4a317a3cbd5..161531491d9 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+CertificateRevocationLists.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+CertificateRevocationLists.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging extension ZMUserSession { diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+EncryptionAtRest.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+EncryptionAtRest.swift index 9aef1bc0d5b..21670d89cb7 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+EncryptionAtRest.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+EncryptionAtRest.swift @@ -19,6 +19,7 @@ import Foundation import LocalAuthentication import WireDataModel +import WireLogging public protocol UserSessionEncryptionAtRestInterface { var encryptMessagesAtRest: Bool { get } diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+IndividualToTeamMigration.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+IndividualToTeamMigration.swift new file mode 100644 index 00000000000..bed7bd73cf9 --- /dev/null +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+IndividualToTeamMigration.swift @@ -0,0 +1,35 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation +import WireAPI +import WireDomain +import WireDomainAPI + +public extension ZMUserSession { + func createIndividualToTeamMigrationUseCase(apiVersion: WireAPI.APIVersion) -> IndividualToTeamMigrationUseCase? { + guard let apiService else { + assertionFailure("apiService is nil") + return nil + } + return IndividualToTeamMigrationUseCaseImplementation( + apiService: apiService, + apiVersion: apiVersion + ) + } +} diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+LifeCycle.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+LifeCycle.swift index 87d24f6a76b..8f7c599c8ab 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+LifeCycle.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+LifeCycle.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging public extension ZMUserSession { diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+OneOnOne.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+OneOnOne.swift index 6f0c06434dc..a1ba0f847ec 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+OneOnOne.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+OneOnOne.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging public extension ZMUserSession { diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Push.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Push.swift index 662b171f8f8..b438bb201b2 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Push.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Push.swift @@ -18,6 +18,7 @@ import Foundation import UserNotifications +import WireLogging import WireRequestStrategy import WireTransport diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+RecurringAction.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+RecurringAction.swift index 4e137d74f76..6e1fea0cc02 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+RecurringAction.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+RecurringAction.swift @@ -18,12 +18,17 @@ import Foundation import WireDataModel +import WireLogging extension ZMUserSession { var updateProteusToMLSMigrationStatusAction: RecurringAction { .init(id: #function, interval: .oneDay) { [weak self] in - guard DeveloperFlag.enableMLSSupport.isOn else { return } + guard + let self, + mlsFeature.isEnabled else { + return + } Task { [weak self] in do { diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+UserSession.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+UserSession.swift index 45b249a78af..fca06ca9c5b 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+UserSession.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+UserSession.swift @@ -369,6 +369,14 @@ extension ZMUserSession: UserSession { CreateConversationFolderUseCase(context: syncContext) } + public func makeSearchUsersUseCase() -> SearchUsersUseCaseProtocol { + SearchUsersUseCase( + context: syncContext, + searchDirectory: SearchDirectory(userSession: self), + isFederationUsageAllowed: isFederationUsageAllowed + ) + } + } extension UInt64 { diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift index 901edd3141b..d7427b4bea9 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession.swift @@ -19,7 +19,9 @@ import Combine import Foundation import WireAnalytics +import WireAPI import WireDataModel +import WireLogging import WireRequestStrategy import WireSystem @@ -28,6 +30,8 @@ typealias UserSessionDelegate = UserSessionAppLockDelegate & UserSessionLogoutDelegate & UserSessionSelfUserClientDelegate +public typealias APIServiceFactory = @Sendable (_ clientID: String, _ userID: UUID) -> APIServiceProtocol + @objcMembers public final class ZMUserSession: NSObject { @@ -40,6 +44,8 @@ public final class ZMUserSession: NSObject { private(set) var isNetworkOnline = true private(set) var coreDataStack: CoreDataStack! + private let apiServiceFactory: APIServiceFactory + private(set) var apiService: APIServiceProtocol? let application: ZMApplication let flowManager: FlowManagerType private(set) var mediaManager: MediaManagerType @@ -193,7 +199,7 @@ public final class ZMUserSession: NSObject { } // swiftlint:disable:next todo_requires_jira_link - public var selfUserClient: UserClient? { // TODO: jacob we don't want this to be public + public var selfUserClient: WireDataModel.UserClient? { // TODO: jacob we don't want this to be public ZMUser.selfUser(in: managedObjectContext).selfClient() } @@ -361,6 +367,7 @@ public final class ZMUserSession: NSObject { transportSession: any TransportSessionType, mediaManager: any MediaManagerType, flowManager: any FlowManagerType, + apiServiceFactory: @escaping @Sendable (_ clientID: String, _ userID: UUID) -> APIServiceProtocol, application: ZMApplication, appVersion: String, coreDataStack: CoreDataStack, @@ -379,6 +386,7 @@ public final class ZMUserSession: NSObject { recurringActionService: any RecurringActionServiceInterface, dependencies: UserSessionDependencies ) { + self.apiServiceFactory = apiServiceFactory self.application = application self.appVersion = appVersion self.flowManager = flowManager @@ -731,7 +739,7 @@ public final class ZMUserSession: NSObject { // MARK: Access Token - private func renewAccessTokenIfNeeded(for userClient: UserClient) { + private func renewAccessTokenIfNeeded(for userClient: WireDataModel.UserClient) { guard let apiVersion = BackendInfo.apiVersion, apiVersion > .v2, @@ -874,6 +882,9 @@ extension ZMUserSession: ZMSyncStateDelegate { managedObjectContext.performGroupedBlock { [weak self] in guard let self else { return } + managedObjectContext.resetMigrationNeedsSlowSyncFlagIfNeeded() + managedObjectContext.resetMigrationNeedsSyncResoucesFlagIfNeeded() + hasCompletedInitialSync = true notificationDispatcher.isEnabled = true delegate?.clientCompletedInitialSync(accountId: account.userIdentifier) @@ -930,7 +941,7 @@ extension ZMUserSession: ZMSyncStateDelegate { } } - if DeveloperFlag.enableMLSSupport.isOn { + if mlsFeature.isEnabled { mlsService.commitPendingProposalsIfNeeded() } @@ -964,7 +975,10 @@ extension ZMUserSession: ZMSyncStateDelegate { private func makeResolveOneOnOneConversationsUseCase(context: NSManagedObjectContext) -> any ResolveOneOnOneConversationsUseCaseProtocol { let supportedProtocolService = SupportedProtocolsService(context: context) - let resolver = OneOnOneResolver(migrator: OneOnOneMigrator(mlsService: mlsService)) + let resolver = OneOnOneResolver( + migrator: OneOnOneMigrator(mlsService: mlsService), + isMLSEnabled: mlsFeature.isEnabled + ) return ResolveOneOnOneConversationsUseCase( context: context, @@ -974,7 +988,7 @@ extension ZMUserSession: ZMSyncStateDelegate { } private func resolveOneOnOneConversationsIfNeeded() async { - guard DeveloperFlag.enableMLSSupport.isOn else { return } + guard mlsFeature.isEnabled else { return } let resolveOneOnOneUseCase = makeResolveOneOnOneConversationsUseCase(context: syncContext) do { @@ -985,7 +999,7 @@ extension ZMUserSession: ZMSyncStateDelegate { } private func performPostQuickSyncE2EIActions() { - guard DeveloperFlag.enableMLSSupport.isOn else { return } + guard mlsFeature.isEnabled else { return } checkExpiredCertificateRevocationLists() checkE2EICertificateExpiryStatus() @@ -1047,14 +1061,14 @@ extension ZMUserSession: ZMSyncStateDelegate { } } - public func didRegisterSelfUserClient(_ userClient: UserClient) { + public func didRegisterSelfUserClient(_ userClient: WireDataModel.UserClient) { // If during registration user allowed notifications, // The push token can only be registered after client registration transportSession.pushChannel.clientID = userClient.remoteIdentifier registerCurrentPushToken() renewAccessTokenIfNeeded(for: userClient) - UserClient.triggerSelfClientCapabilityUpdate(syncContext) + WireDataModel.UserClient.triggerSelfClientCapabilityUpdate(syncContext) managedObjectContext.performGroupedBlock { [weak self] in guard @@ -1078,6 +1092,11 @@ extension ZMUserSession: ZMSyncStateDelegate { let clientId = userClient.safeRemoteIdentifier.safeForLoggingDescription WireLogger.authentication.addTag(.selfClientId, value: clientId) + guard let selfUserId = ZMUser.selfUser(in: syncContext).remoteIdentifier else { + assertionFailure("unable to find selfUser from syncContext,") + return + } + apiService = apiServiceFactory(clientId, selfUserId) } public func didFailToRegisterSelfUserClient(error: Error) { diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSessionBuilder.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSessionBuilder.swift index cfe19773887..02f96f7f58a 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSessionBuilder.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSessionBuilder.swift @@ -17,6 +17,7 @@ // import Foundation +import WireAPI import WireDataModel import WireRequestStrategy import WireUtilities @@ -25,6 +26,7 @@ struct ZMUserSessionBuilder { // MARK: - Properties + private var apiServiceFactory: APIServiceFactory? private var appVersion: String? private var appLock: (any AppLockType)? private var application: (any ZMApplication)? @@ -55,6 +57,7 @@ struct ZMUserSessionBuilder { func build() -> ZMUserSession { guard + let apiServiceFactory, let appVersion, let appLock, let application, @@ -85,6 +88,7 @@ struct ZMUserSessionBuilder { transportSession: transportSession, mediaManager: mediaManager, flowManager: flowManager, + apiServiceFactory: apiServiceFactory, application: application, appVersion: appVersion, coreDataStack: coreDataStack, @@ -108,6 +112,7 @@ struct ZMUserSessionBuilder { // MARK: - Setup Dependencies mutating func withAllDependencies( + apiServiceFactory: @escaping APIServiceFactory, appVersion: String, application: any ZMApplication, cryptoboxMigrationManager: any CryptoboxMigrationManagerInterface, @@ -195,6 +200,7 @@ struct ZMUserSessionBuilder { // setup builder + self.apiServiceFactory = apiServiceFactory self.appVersion = appVersion self.appLock = appLock self.application = application diff --git a/wire-ios-sync-engine/Support/Sourcery/config.yml b/wire-ios-sync-engine/Support/Sourcery/config.yml index 1878cd93016..fdc0b7e691d 100644 --- a/wire-ios-sync-engine/Support/Sourcery/config.yml +++ b/wire-ios-sync-engine/Support/Sourcery/config.yml @@ -5,6 +5,5 @@ templates: output: ./generated args: - autoMockableTestableImports: ["WireSyncEngine"] autoMockableImports: ["WireAnalytics"] - + autoMockableTestableImports: ["WireSyncEngine"] diff --git a/wire-ios-sync-engine/Support/Sourcery/generated/AutoMockable.generated.swift b/wire-ios-sync-engine/Support/Sourcery/generated/AutoMockable.generated.swift index e29afc30e49..77f55f487ff 100644 --- a/wire-ios-sync-engine/Support/Sourcery/generated/AutoMockable.generated.swift +++ b/wire-ios-sync-engine/Support/Sourcery/generated/AutoMockable.generated.swift @@ -24,14 +24,8 @@ // swiftlint:disable line_length // swiftlint:disable variable_name -public import Foundation -#if os(iOS) || os(tvOS) || os(watchOS) -public import UIKit -#elseif os(OSX) -public import AppKit -#endif -public import WireAnalytics +import WireAnalytics @testable import WireSyncEngine @@ -556,6 +550,38 @@ public class MockResolveOneOnOneConversationsUseCaseProtocol: ResolveOneOnOneCon } +public class MockSearchUsersUseCaseProtocol: SearchUsersUseCaseProtocol { + + // MARK: - Life cycle + + public init() {} + + + // MARK: - invoke + + public var invokeQueryOptionsMessageProtocol_Invocations: [(query: String, options: SearchOptions, messageProtocol: MessageProtocol?)] = [] + public var invokeQueryOptionsMessageProtocol_MockError: Error? + public var invokeQueryOptionsMessageProtocol_MockMethod: ((String, SearchOptions, MessageProtocol?) async throws -> SearchResult)? + public var invokeQueryOptionsMessageProtocol_MockValue: SearchResult? + + public func invoke(query: String, options: SearchOptions, messageProtocol: MessageProtocol?) async throws -> SearchResult { + invokeQueryOptionsMessageProtocol_Invocations.append((query: query, options: options, messageProtocol: messageProtocol)) + + if let error = invokeQueryOptionsMessageProtocol_MockError { + throw error + } + + if let mock = invokeQueryOptionsMessageProtocol_MockMethod { + return try await mock(query, options, messageProtocol) + } else if let mock = invokeQueryOptionsMessageProtocol_MockValue { + return mock + } else { + fatalError("no mock for `invokeQueryOptionsMessageProtocol`") + } + } + +} + public class MockSecurityClassificationProviding: SecurityClassificationProviding { // MARK: - Life cycle diff --git a/wire-ios-sync-engine/Tests/Source/E2EE/UserClientRequestStrategyTests.swift b/wire-ios-sync-engine/Tests/Source/E2EE/UserClientRequestStrategyTests.swift index 53312b850e9..3e92e0fe60b 100644 --- a/wire-ios-sync-engine/Tests/Source/E2EE/UserClientRequestStrategyTests.swift +++ b/wire-ios-sync-engine/Tests/Source/E2EE/UserClientRequestStrategyTests.swift @@ -771,7 +771,7 @@ extension UserClientRequestStrategyTests { "email": self.clientUpdateStatus.mockCredentials.email!, "password": self.clientUpdateStatus.mockCredentials.password! ]) - XCTAssertEqual($0.method, ZMTransportRequestMethod.delete) + XCTAssertEqual($0.method, .delete) } } } diff --git a/wire-ios-sync-engine/Tests/Source/MockAddressBook.swift b/wire-ios-sync-engine/Tests/Source/MockAddressBook.swift deleted file mode 100644 index b812f3c8130..00000000000 --- a/wire-ios-sync-engine/Tests/Source/MockAddressBook.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Foundation -@testable import WireSyncEngine - -/// Fake to supply predefined AB hashes -class MockAddressBook: WireSyncEngine.AddressBook, WireSyncEngine.AddressBookAccessor { - - /// Find contact by Id - func contact(identifier: String) -> WireSyncEngine.ContactRecord? { - contacts.first { $0.localIdentifier == identifier } - } - - /// List of contacts in this address book - var contacts = [MockAddressBookContact]() - - /// Reported number of contacts (it might be higher than `fakeContacts` - /// because some contacts are filtered for not having valid email/phone) - var numberOfAdditionalContacts: UInt = 0 - - var numberOfContacts: UInt { - UInt(contacts.count) + numberOfAdditionalContacts - } - - /// Enumerates the contacts, invoking the block for each contact. - /// If the block returns false, it will stop enumerating them. - func enumerateRawContacts(block: @escaping (WireSyncEngine.ContactRecord) -> (Bool)) { - for contact in contacts where !block(contact) { - return - } - let infiniteContact = MockAddressBookContact( - firstName: "johnny infinite", - emailAddresses: ["johnny.infinite@example.com"], - phoneNumbers: [] - ) - while createInfiniteContacts { - if !block(infiniteContact) { - return - } - } - } - - func rawContacts(matchingQuery: String) -> [WireSyncEngine.ContactRecord] { - guard matchingQuery != "" else { - return contacts - } - return contacts - .filter { - $0.firstName.lowercased().contains(matchingQuery.lowercased()) || $0.lastName.lowercased() - .contains(matchingQuery.lowercased()) - } - } - - /// Replace the content with a given number of random hashes - func fillWithContacts(_ number: UInt) { - contacts = (0 ..< number).map { - self.createContact(card: $0) - } - } - - /// Create a fake contact - func createContact(card: UInt) -> MockAddressBookContact { - MockAddressBookContact( - firstName: "tester \(card)", - emailAddresses: ["tester_\(card)@example.com"], - phoneNumbers: ["+155512300\(card % 10)"], - identifier: "\(card)" - ) - } - - /// Generate an infinite number of contacts - var createInfiniteContacts = false -} - -struct MockAddressBookContact: WireSyncEngine.ContactRecord { - - static var incrementalLocalIdentifier = ZMAtomicInteger(integer: 0) - - var firstName = "" - var lastName = "" - var middleName = "" - var rawEmails: [String] - var rawPhoneNumbers: [String] - var nickname = "" - var organization = "" - var localIdentifier = "" - - init(firstName: String, emailAddresses: [String], phoneNumbers: [String], identifier: String? = nil) { - self.firstName = firstName - self.rawEmails = emailAddresses - self.rawPhoneNumbers = phoneNumbers - self.localIdentifier = identifier ?? { - MockAddressBookContact.incrementalLocalIdentifier.increment() - return "\(MockAddressBookContact.incrementalLocalIdentifier.rawValue)" - }() - } - - var expectedHashes: [String] { - rawEmails.map(\.base64EncodedSHADigest) + rawPhoneNumbers.map(\.base64EncodedSHADigest) - } -} diff --git a/wire-ios-sync-engine/Tests/Source/Mocks/MockAPIService.swift b/wire-ios-sync-engine/Tests/Source/Mocks/MockAPIService.swift new file mode 100644 index 00000000000..3e2298d4447 --- /dev/null +++ b/wire-ios-sync-engine/Tests/Source/Mocks/MockAPIService.swift @@ -0,0 +1,37 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireAPI + +public class MockAPIService: APIServiceProtocol { + let requestHandler: (@Sendable (_ request: URLRequest, _ requiringAccessToken: Bool) async throws -> ( + Data, + HTTPURLResponse + ))? + + public init(requestHandler: (@Sendable (_: URLRequest, _: Bool) async throws -> (Data, HTTPURLResponse))? = nil) { + self.requestHandler = requestHandler + } + + public func executeRequest( + _ request: URLRequest, + requiringAccessToken: Bool + ) async throws -> (Data, HTTPURLResponse) { + try await requestHandler?(request, requiringAccessToken) ?? (Data(), HTTPURLResponse()) + } +} diff --git a/wire-ios-sync-engine/Tests/Source/Registration/AddressBookTests.swift b/wire-ios-sync-engine/Tests/Source/Registration/AddressBookTests.swift deleted file mode 100644 index edbbcfbf4fe..00000000000 --- a/wire-ios-sync-engine/Tests/Source/Registration/AddressBookTests.swift +++ /dev/null @@ -1,526 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import AddressBook -import Foundation -@testable import WireSyncEngine - -final class AddressBookTests: XCTestCase { - - fileprivate var addressBook: MockAddressBook! - - override func setUp() { - addressBook = MockAddressBook() - super.setUp() - } - - override func tearDown() { - addressBook = nil - super.tearDown() - } -} - -// MARK: - Access to AB - -extension AddressBookTests { - - func testThatItReturnsAllContactsWhenTheyHaveValidEmailAndPhoneNumbers() { - - // given - addressBook.contacts = [ - MockAddressBookContact( - firstName: "Olaf", - emailAddresses: ["olaf@example.com", "janet@example.com"], - phoneNumbers: ["+15550100"] - ), - MockAddressBookContact( - firstName: "สยาม", - emailAddresses: ["siam@example.com"], - phoneNumbers: ["+15550101", "+15550102"] - ) - ] - - // when - let contacts = Array(addressBook.contacts(range: 0 ..< 100)) - - // then - XCTAssertEqual(contacts.count, 2) - for i in 0 ..< addressBook.contacts.count { - XCTAssertEqual(contacts[i].emailAddresses, addressBook.contacts[i].rawEmails) - XCTAssertEqual(contacts[i].phoneNumbers, addressBook.contacts[i].rawPhoneNumbers) - } - } - - func testThatItReturnsAllContactsWhenTheyHaveValidEmailOrPhoneNumbers() { - - // given - addressBook.contacts = [ - MockAddressBookContact(firstName: "Olaf", emailAddresses: ["olaf@example.com"], phoneNumbers: []), - MockAddressBookContact(firstName: "สยาม", emailAddresses: [], phoneNumbers: ["+15550101"]) - ] - - // when - let contacts = Array(addressBook.contacts(range: 0 ..< 100)) - - // then - XCTAssertEqual(contacts.count, 2) - for i in 0 ..< addressBook.contacts.count { - XCTAssertEqual(contacts[i].emailAddresses, addressBook.contacts[i].rawEmails) - XCTAssertEqual(contacts[i].phoneNumbers, addressBook.contacts[i].rawPhoneNumbers) - } - } - - func testThatItFilterlContactsThatHaveNoEmailNorPhone() { - - // given - addressBook.contacts = [ - MockAddressBookContact( - firstName: "Olaf", - emailAddresses: ["olaf@example.com"], - phoneNumbers: ["+15550100"] - ), - MockAddressBookContact(firstName: "สยาม", emailAddresses: [], phoneNumbers: []) - ] - - // when - let contacts = Array(addressBook.contacts(range: 0 ..< 100)) - - // then - XCTAssertEqual(contacts.count, 1) - XCTAssertEqual(contacts[0].emailAddresses, addressBook.contacts[0].rawEmails) - } -} - -// MARK: - Validation/normalization - -extension AddressBookTests { - - func testThatItFilterlContactsThatHaveAnInvalidPhoneAndNoEmail() { - - // given - addressBook.contacts = [ - MockAddressBookContact(firstName: "Olaf", emailAddresses: [], phoneNumbers: ["aabbccdd"]) - ] - - // when - let contacts = Array(addressBook.contacts(range: 0 ..< 100)) - - // then - XCTAssertEqual(contacts.count, 0) - } - - func testThatIgnoresInvalidPhones() { - - // given - addressBook.contacts = [ - MockAddressBookContact(firstName: "Olaf", emailAddresses: ["janet@example.com"], phoneNumbers: ["aabbccdd"]) - ] - - // when - let contacts = Array(addressBook.contacts(range: 0 ..< 100)) - - // then - XCTAssertEqual(contacts.count, 1) - XCTAssertEqual(contacts[0].emailAddresses, addressBook.contacts[0].rawEmails) - XCTAssertEqual(contacts[0].phoneNumbers, []) - } - - func testThatItFilterlContactsThatHaveNoPhoneAndInvalidEmail() { - - // given - addressBook.contacts = [ - MockAddressBookContact(firstName: "Olaf", emailAddresses: ["janet"], phoneNumbers: []) - ] - - // when - let contacts = Array(addressBook.contacts(range: 0 ..< 100)) - - // then - XCTAssertEqual(contacts.count, 0) - } - - func testThatIgnoresInvalidEmails() { - - // given - addressBook.contacts = [ - MockAddressBookContact(firstName: "Olaf", emailAddresses: ["janet"], phoneNumbers: ["+15550103"]) - ] - - // when - let contacts = Array(addressBook.contacts(range: 0 ..< 100)) - - // then - XCTAssertEqual(contacts.count, 1) - XCTAssertEqual(contacts[0].emailAddresses, []) - XCTAssertEqual(contacts[0].phoneNumbers, addressBook.contacts[0].rawPhoneNumbers) - } - - func testThatItNormalizesPhoneNumbers() { - - // given - addressBook.contacts = [ - MockAddressBookContact(firstName: "Olaf", emailAddresses: [], phoneNumbers: ["+1 (555) 0103"]) - ] - - // when - let contacts = Array(addressBook.contacts(range: 0 ..< 100)) - - // then - XCTAssertEqual(contacts.count, 1) - XCTAssertEqual(contacts[0].phoneNumbers, ["+15550103"]) - } - - func testThatItNormalizesEmails() { - - // given - addressBook.contacts = [ - MockAddressBookContact( - firstName: "Olaf", - emailAddresses: ["Olaf Karlsson "], - phoneNumbers: [] - ) - ] - - // when - let contacts = Array(addressBook.contacts(range: 0 ..< 100)) - - // then - XCTAssertEqual(contacts.count, 1) - XCTAssertEqual(contacts[0].emailAddresses, ["janet+1@example.com"]) - } - - func testThatItDoesNotIgnoresPhonesWithPlusZero() { - - // given - addressBook.contacts = [ - MockAddressBookContact(firstName: "Olaf", emailAddresses: [], phoneNumbers: ["+012345678"]) - ] - - // when - let contacts = Array(addressBook.contacts(range: 0 ..< 100)) - - // then - XCTAssertEqual(contacts.count, 1) - XCTAssertEqual(contacts[0].phoneNumbers, ["+012345678"]) - } -} - -// MARK: - Encoding - -extension AddressBookTests { - - func testThatItEncodesUsers() { - - // given - addressBook.contacts = [ - MockAddressBookContact( - firstName: "Olaf", - emailAddresses: ["olaf@example.com"], - phoneNumbers: ["+15550101"] - ), - MockAddressBookContact(firstName: "สยาม", emailAddresses: [], phoneNumbers: ["+15550100"]), - MockAddressBookContact(firstName: "Hadiya", emailAddresses: [], phoneNumbers: ["+15550102"]) - - ] - let queue = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - queue.createDispatchGroups() - let expectation = expectation(description: "Callback invoked") - - // when - addressBook.encodeWithCompletionHandler(queue, startingContactIndex: 0, maxNumberOfContacts: 100) { chunk in - - // then - if let chunk { - XCTAssertEqual(chunk.numberOfTotalContacts, 3) - XCTAssertEqual(chunk.includedContacts, UInt(0) ..< UInt(3)) - let expected = [ - self.addressBook.contacts[0].localIdentifier: [ - "BSdmiT9F5EtQrsfcGm+VC7Ofb0ZRREtCGCFw4TCimqk=", - "f9KRVqKI/n1886fb6FnP4oIORkG5S2HO0BoCYOxLFaA=" - ], - self.addressBook.contacts[1].localIdentifier: ["YCzX+75BaI4tkCJLysNi2y8f8uK6dIfYWFyc4ibLbQA="], - self.addressBook.contacts[2].localIdentifier: ["iJXG3rJ3vc8rrh7EgHzbWPZsWOHFJ7mYv/MD6DlY154="] - ] - checkEqual(lhs: chunk.otherContactsHashes, rhs: expected) - } else { - XCTFail() - } - expectation.fulfill() - } - - waitForExpectations(timeout: 0.5) { error in - XCTAssertNil(error) - } - } - - func testThatItCallsCompletionHandlerWithNilIfNoContacts() { - - // given - addressBook.contacts = [] - let queue = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - queue.createDispatchGroups() - let expectation = expectation(description: "Callback invoked") - - // when - addressBook.encodeWithCompletionHandler(queue, startingContactIndex: 0, maxNumberOfContacts: 100) { chunk in - - // then - XCTAssertNil(chunk) - expectation.fulfill() - } - - waitForExpectations(timeout: 0.5) { error in - XCTAssertNil(error) - } - } - - func testThatItEncodesOnlyAMaximumNumberOfUsers() { - - // given - addressBook.contacts = [ - MockAddressBookContact( - firstName: "Olaf", - emailAddresses: ["olaf@example.com"], - phoneNumbers: ["+15550101"] - ), - MockAddressBookContact(firstName: "สยาม", emailAddresses: [], phoneNumbers: ["+15550100"]), - MockAddressBookContact(firstName: "Hadiya", emailAddresses: [], phoneNumbers: ["+15550102"]) - - ] - let queue = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - queue.createDispatchGroups() - let expectation = expectation(description: "Callback invoked") - - // when - addressBook.encodeWithCompletionHandler(queue, startingContactIndex: 0, maxNumberOfContacts: 2) { chunk in - - // then - if let chunk { - XCTAssertEqual(chunk.numberOfTotalContacts, 3) - XCTAssertEqual(chunk.includedContacts, UInt(0) ..< UInt(2)) - let expected = [ - self.addressBook.contacts[0].localIdentifier: [ - "BSdmiT9F5EtQrsfcGm+VC7Ofb0ZRREtCGCFw4TCimqk=", - "f9KRVqKI/n1886fb6FnP4oIORkG5S2HO0BoCYOxLFaA=" - ], - self.addressBook.contacts[1].localIdentifier: ["YCzX+75BaI4tkCJLysNi2y8f8uK6dIfYWFyc4ibLbQA="] - ] - checkEqual(lhs: chunk.otherContactsHashes, rhs: expected) - } else { - XCTFail() - } - expectation.fulfill() - } - - waitForExpectations(timeout: 0.5) { error in - XCTAssertNil(error) - } - } - - func testThatItEncodesOnlyTheRequestedUsers() { - - // given - addressBook.contacts = [ - MockAddressBookContact( - firstName: "Olaf", - emailAddresses: ["olaf@example.com"], - phoneNumbers: ["+15550101"] - ), - MockAddressBookContact(firstName: "สยาม", emailAddresses: [], phoneNumbers: ["+15550100"]), - MockAddressBookContact(firstName: "Hadiya", emailAddresses: [], phoneNumbers: ["+15550102"]), - MockAddressBookContact(firstName: " أميرة", emailAddresses: ["a@example.com"], phoneNumbers: []) - ] - - let queue = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - queue.createDispatchGroups() - let expectation = expectation(description: "Callback invoked") - - // when - addressBook.encodeWithCompletionHandler(queue, startingContactIndex: 1, maxNumberOfContacts: 2) { chunk in - - // then - if let chunk { - XCTAssertEqual(chunk.numberOfTotalContacts, 4) - XCTAssertEqual(chunk.includedContacts, UInt(1) ..< UInt(3)) - let expected = [ - self.addressBook.contacts[1].localIdentifier: ["YCzX+75BaI4tkCJLysNi2y8f8uK6dIfYWFyc4ibLbQA="], - self.addressBook.contacts[2].localIdentifier: ["iJXG3rJ3vc8rrh7EgHzbWPZsWOHFJ7mYv/MD6DlY154="] - ] - checkEqual(lhs: chunk.otherContactsHashes, rhs: expected) - } else { - XCTFail() - } - expectation.fulfill() - } - - waitForExpectations(timeout: 0.5) { error in - XCTAssertNil(error) - } - } - - func testThatItEncodesAsManyContactsAsItCanIfAskedToEncodeTooMany() { - - // given - addressBook.contacts = [ - MockAddressBookContact( - firstName: "Olaf", - emailAddresses: ["olaf@example.com"], - phoneNumbers: ["+15550101"] - ), - MockAddressBookContact(firstName: " أميرة", emailAddresses: ["a@example.com"], phoneNumbers: []), - MockAddressBookContact(firstName: "สยาม", emailAddresses: [], phoneNumbers: ["+15550100"]), - MockAddressBookContact(firstName: "Hadiya", emailAddresses: [], phoneNumbers: ["+15550102"]) - ] - - let queue = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - queue.createDispatchGroups() - let expectation = expectation(description: "Callback invoked") - - // when - addressBook.encodeWithCompletionHandler(queue, startingContactIndex: 2, maxNumberOfContacts: 20) { chunk in - - // then - if let chunk { - XCTAssertEqual(chunk.numberOfTotalContacts, 4) - XCTAssertEqual(chunk.includedContacts, UInt(2) ..< UInt(4)) - let expected = [ - self.addressBook.contacts[2].localIdentifier: ["YCzX+75BaI4tkCJLysNi2y8f8uK6dIfYWFyc4ibLbQA="], - self.addressBook.contacts[3].localIdentifier: ["iJXG3rJ3vc8rrh7EgHzbWPZsWOHFJ7mYv/MD6DlY154="] - ] - checkEqual(lhs: chunk.otherContactsHashes, rhs: expected) - } else { - XCTFail() - } - expectation.fulfill() - } - - waitForExpectations(timeout: 0.5) { error in - XCTAssertNil(error) - } - } - - func testThatItEncodesNoContactIfAskedToEncodePastTheLastContact() { - - // given - addressBook.contacts = [ - MockAddressBookContact( - firstName: "Olaf", - emailAddresses: ["olaf@example.com"], - phoneNumbers: ["+15550101"] - ), - MockAddressBookContact(firstName: " أميرة", emailAddresses: ["a@example.com"], phoneNumbers: []), - MockAddressBookContact(firstName: "สยาม", emailAddresses: [], phoneNumbers: ["+15550100"]) - ] - - let queue = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - queue.createDispatchGroups() - let expectation = expectation(description: "Callback invoked") - - // when - addressBook.encodeWithCompletionHandler(queue, startingContactIndex: 20, maxNumberOfContacts: 20) { chunk in - - // then - if let chunk { - XCTAssertEqual(chunk.numberOfTotalContacts, 3) - XCTAssertEqual(chunk.includedContacts, UInt(20) ..< UInt(20)) - XCTAssertEqual(chunk.otherContactsHashes.count, 0) - } else { - XCTFail() - } - expectation.fulfill() - } - - waitForExpectations(timeout: 0.5) { error in - XCTAssertNil(error) - } - } - - func testThatItEncodesTheSameAddressBookInTheSameWay() { - - // given - addressBook.contacts = [ - MockAddressBookContact( - firstName: "Olaf", - emailAddresses: ["olaf@example.com"], - phoneNumbers: ["+15550101"] - ), - MockAddressBookContact(firstName: "สยาม", emailAddresses: [], phoneNumbers: ["+15550100"]), - MockAddressBookContact(firstName: "Hadiya", emailAddresses: [], phoneNumbers: ["+15550102"]) - - ] - let queue = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - queue.createDispatchGroups() - let expectation1 = expectation(description: "Callback invoked once") - - var chunk1: [String: [String]]? - var chunk2: [String: [String]]? - - // when - addressBook.encodeWithCompletionHandler(queue, startingContactIndex: 0, maxNumberOfContacts: 100) { chunk in - - chunk1 = chunk?.otherContactsHashes - expectation1.fulfill() - } - - waitForExpectations(timeout: 0.5) { error in - XCTAssertNil(error) - } - - let expectation2 = expectation(description: "Callback invoked twice") - addressBook.encodeWithCompletionHandler(queue, startingContactIndex: 0, maxNumberOfContacts: 100) { chunk in - - chunk2 = chunk?.otherContactsHashes - expectation2.fulfill() - } - - waitForExpectations(timeout: 0.5) { error in - XCTAssertNil(error) - } - - // then - checkEqual(lhs: chunk1, rhs: chunk2) - } -} - -// MARK: - Helpers - -private func checkEqual( - lhs: [String: [String]]?, - rhs: [String: [String]]?, - line: UInt = #line, - file: StaticString = #filePath -) { - guard let lhs, let rhs else { - XCTFail("Value is nil", file: file, line: line) - return - } - - let keys1 = Set(lhs.keys) - let keys2 = Set(rhs.keys) - guard keys1 == keys2 else { - XCTAssertEqual(keys1, keys2, file: file, line: line) - return - } - - for key in keys1 { - let array1 = lhs[key]! - let array2 = rhs[key]! - zip(array1, array2).forEach { XCTAssertEqual($0.0, $0.1, file: file, line: line) } - } - -} diff --git a/wire-ios-sync-engine/Tests/Source/SessionManager/APIMigrationManagerTests.swift b/wire-ios-sync-engine/Tests/Source/SessionManager/APIMigrationManagerTests.swift index cf63b6bc6e6..3e4a580620f 100644 --- a/wire-ios-sync-engine/Tests/Source/SessionManager/APIMigrationManagerTests.swift +++ b/wire-ios-sync-engine/Tests/Source/SessionManager/APIMigrationManagerTests.swift @@ -258,6 +258,7 @@ final class APIMigrationManagerTests: MessagingTest { var builder = ZMUserSessionBuilder() builder.withAllDependencies( + apiServiceFactory: { _, _ in MockAPIService() }, appVersion: "999", application: application, cryptoboxMigrationManager: mockCryptoboxMigrationManager, diff --git a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/AssetDeletionRequestStrategyTests.swift b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/AssetDeletionRequestStrategyTests.swift index 6c5bd84978f..317243e4ad9 100644 --- a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/AssetDeletionRequestStrategyTests.swift +++ b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/AssetDeletionRequestStrategyTests.swift @@ -131,7 +131,7 @@ extension AssetDeletionRequestStrategyTests { "/assets/v3/\(identifier)" case .v1: "/v1/assets/v3/\(identifier)" - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: "/v\(apiVersion.rawValue)/assets/\(domain)/\(identifier)" } XCTAssertNotNil(request) diff --git a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/DeleteAccountRequestStrategyTests.swift b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/DeleteAccountRequestStrategyTests.swift index 4c36d66b704..99c4911509e 100644 --- a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/DeleteAccountRequestStrategyTests.swift +++ b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/DeleteAccountRequestStrategyTests.swift @@ -61,7 +61,7 @@ class DeleteAccountRequestStrategyTests: MessagingTest, AccountDeletedObserver { // then if let request { - XCTAssertEqual(request.method, ZMTransportRequestMethod.delete) + XCTAssertEqual(request.method, .delete) XCTAssertEqual(request.path, "/self") XCTAssertTrue(request.needsAuthentication) } else { diff --git a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/ProxiedRequestStrategyTests.swift b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/ProxiedRequestStrategyTests.swift index 9082243a57f..84614dec2d5 100644 --- a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/ProxiedRequestStrategyTests.swift +++ b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/ProxiedRequestStrategyTests.swift @@ -62,7 +62,7 @@ class ProxiedRequestStrategyTests: MessagingTest { // then if let request { - XCTAssertEqual(request.method, ZMTransportRequestMethod.get) + XCTAssertEqual(request.method, .get) XCTAssertEqual(request.path, "/proxy/giphy/foo/bar") XCTAssertTrue(request.needsAuthentication) } else { @@ -85,7 +85,7 @@ class ProxiedRequestStrategyTests: MessagingTest { // then if let request { - XCTAssertEqual(request.method, ZMTransportRequestMethod.get) + XCTAssertEqual(request.method, .get) XCTAssertEqual(request.path, "/proxy/soundcloud/foo/bar") XCTAssertTrue(request.needsAuthentication) XCTAssertTrue(request.doesNotFollowRedirects) @@ -109,7 +109,7 @@ class ProxiedRequestStrategyTests: MessagingTest { // then if let request { - XCTAssertEqual(request.method, ZMTransportRequestMethod.get) + XCTAssertEqual(request.method, .get) XCTAssertEqual(request.path, "/proxy/youtube/foo/bar") XCTAssertTrue(request.needsAuthentication) } else { diff --git a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/SearchUserImageStrategyTests.swift b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/SearchUserImageStrategyTests.swift index 37085990df5..fed5ba2b3d0 100644 --- a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/SearchUserImageStrategyTests.swift +++ b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/SearchUserImageStrategyTests.swift @@ -307,7 +307,7 @@ final class SearchUserImageStrategyTests: MessagingTest { "/assets/v3/\(assetID)" case .v1: "/v1/assets/v4/\(domain)/\(assetID)" - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: "/v\(apiVersion.rawValue)/assets/\(domain)/\(assetID)" } diff --git a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/SignatureRequestStrategyTests.swift b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/SignatureRequestStrategyTests.swift index db2917e37bc..0a7f3ca3193 100644 --- a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/SignatureRequestStrategyTests.swift +++ b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/SignatureRequestStrategyTests.swift @@ -69,7 +69,7 @@ class SignatureRequestStrategyTests: MessagingTest { XCTAssertEqual(payload?["hash"] as? String, syncMOC.signatureStatus?.encodedHash) } XCTAssertEqual(request?.path, "/signature/request") - XCTAssertEqual(request?.method, ZMTransportRequestMethod.post) + XCTAssertEqual(request?.method, .post) } func testThatItGeneratesCorrectRequestIfStateIsWaitingForSignature() { @@ -96,7 +96,7 @@ class SignatureRequestStrategyTests: MessagingTest { // then XCTAssertNotNil(request) XCTAssertEqual(request?.path, "/signature/pending/\(responseId)") - XCTAssertEqual(request?.method, ZMTransportRequestMethod.get) + XCTAssertEqual(request?.method, .get) } func testThatItNotifiesSignatureStatusAfterSuccessfulResponseToReceiveConsentURL() { diff --git a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/UserImageAssetUpdateStrategyTests.swift b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/UserImageAssetUpdateStrategyTests.swift index f54121488e8..1a859687269 100644 --- a/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/UserImageAssetUpdateStrategyTests.swift +++ b/wire-ios-sync-engine/Tests/Source/Synchronization/Strategies/UserImageAssetUpdateStrategyTests.swift @@ -352,7 +352,7 @@ class UserImageAssetUpdateStrategyTests: MessagingTest { "/assets/v3/\(assetId)" case .v1: "/v1/assets/v4/\(domain)/\(assetId)" - case .v2, .v3, .v4, .v5, .v6: + case .v2, .v3, .v4, .v5, .v6, .v7: "/v\(apiVersion.rawValue)/assets/\(domain)/\(assetId)" } diff --git a/wire-ios-sync-engine/Tests/Source/Synchronization/SynchronizationMocks.swift b/wire-ios-sync-engine/Tests/Source/Synchronization/SynchronizationMocks.swift index cea4f11973f..6bc63f7a596 100644 --- a/wire-ios-sync-engine/Tests/Source/Synchronization/SynchronizationMocks.swift +++ b/wire-ios-sync-engine/Tests/Source/Synchronization/SynchronizationMocks.swift @@ -16,7 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import AddressBook import avs import Foundation import WireCryptobox diff --git a/wire-ios-sync-engine/Tests/Source/Synchronization/Transcoders/UserProfileUpdateRequestStrategyTests.swift b/wire-ios-sync-engine/Tests/Source/Synchronization/Transcoders/UserProfileUpdateRequestStrategyTests.swift index e0883b93f02..cad699d6f0f 100644 --- a/wire-ios-sync-engine/Tests/Source/Synchronization/Transcoders/UserProfileUpdateRequestStrategyTests.swift +++ b/wire-ios-sync-engine/Tests/Source/Synchronization/Transcoders/UserProfileUpdateRequestStrategyTests.swift @@ -17,6 +17,7 @@ // import XCTest + @testable import WireSyncEngine class UserProfileUpdateRequestStrategyTests: MessagingTest { diff --git a/wire-ios-sync-engine/Tests/Source/Synchronization/Transcoders/ZMUserImageTranscoderTests.m b/wire-ios-sync-engine/Tests/Source/Synchronization/Transcoders/ZMUserImageTranscoderTests.m deleted file mode 100644 index 8e5498d0e41..00000000000 --- a/wire-ios-sync-engine/Tests/Source/Synchronization/Transcoders/ZMUserImageTranscoderTests.m +++ /dev/null @@ -1,791 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -@import zimages; -@import MobileCoreServices; -@import ZMTransport; -@import ZMCMockTransport; -@import WireSyncEngine; -@import ZMCDataModel; -@import WireRequestStrategy; - -#import "MessagingTest.h" -#import "ZMUserImageTranscoder.h" -#import "ZMUserImageTranscoder+Testing.h" - - -@interface ZMUserImageTranscoderTests : MessagingTest - -@property (nonatomic) ZMUserImageTranscoder *sut; -@property (nonatomic) ZMUser *user1; -@property (nonatomic) NSOperationQueue *queue; -@property (nonatomic) NSUUID *user1ID; - -@end - - -@implementation ZMUserImageTranscoderTests - -- (void)setUp -{ - [super setUp]; - - self.syncMOC.zm_userImageCache = [[UserImageLocalCache alloc] initWithLocation:nil]; - self.uiMOC.zm_userImageCache = self.syncMOC.zm_userImageCache; - - self.queue = [[NSOperationQueue alloc] init]; - self.queue.name = self.name; - self.queue.maxConcurrentOperationCount = 1; - - self.sut = (id) [[ZMUserImageTranscoder alloc] initWithManagedObjectContext:self.syncMOC imageProcessingQueue:self.queue]; - WaitForAllGroupsToBeEmpty(0.5); - - self.user1ID = [NSUUID createUUID]; - [self.syncMOC performGroupedBlockAndWait:^{ - self.user1 = [ZMUser insertNewObjectInManagedObjectContext:self.syncMOC]; - self.user1.remoteIdentifier = self.user1ID; - - XCTAssert([self.syncMOC saveOrRollback]); - }]; -} - -- (void)tearDown -{ - [self.sut tearDown]; - self.sut = nil; - self.user1 = nil; - self.user1ID = nil; - [super tearDown]; -} - -@end - - - -@implementation ZMUserImageTranscoderTests (DownloadPredicate) - -- (void)testThatItMatchesUserWithoutALocalImage; -{ - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSPredicate *p = [ZMUser predicateForMediumImageNeedingToBeUpdatedFromBackend]; - - // when - self.user1.mediumRemoteIdentifier = [NSUUID createUUID]; - self.user1.localMediumRemoteIdentifier = nil; - - // then - XCTAssertTrue([p evaluateWithObject:self.user1]); - }]; -} - -- (void)testThatItMatchesUserWithALocalImageAndNoRemoteImage -{ - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSPredicate *p = [ZMUser predicateForMediumImageNeedingToBeUpdatedFromBackend]; - - // when - self.user1.mediumRemoteIdentifier = [NSUUID createUUID]; - self.user1.localMediumRemoteIdentifier = [NSUUID createUUID]; - - // then - XCTAssertTrue([p evaluateWithObject:self.user1]); - }]; -} - - - -- (void)testThatItDoesNotMatchAUserWithNoLocalImageAndNoRemoteImage -{ - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSPredicate *p = [ZMUser predicateForMediumImageNeedingToBeUpdatedFromBackend]; - - // when - self.user1.mediumRemoteIdentifier = nil; - self.user1.localMediumRemoteIdentifier = nil; - - // then - XCTAssertFalse([p evaluateWithObject:self.user1]); - }]; -} - -- (void)testThatItDoesNotMatchAUserWithALocalImageButNoRemoteImage; -{ - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSPredicate *p = [ZMUser predicateForMediumImageNeedingToBeUpdatedFromBackend]; - - // when - self.user1.mediumRemoteIdentifier = nil; - self.user1.localMediumRemoteIdentifier = [NSUUID createUUID]; - - // then - XCTAssertFalse([p evaluateWithObject:self.user1]); - }]; -} - -@end - - - -@implementation ZMUserImageTranscoderTests (DownloadFilter) - -- (void)testThatItDoesntFilterSelfUserWithALocalImageAndADifferentRemoteImage -{ - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSPredicate *p = [ZMUser predicateForMediumImageDownloadFilter]; - - // when - ZMUser *selfUser = [ZMUser selfUserInContext:self.syncMOC]; - selfUser.remoteIdentifier = [NSUUID createUUID]; - selfUser.mediumRemoteIdentifier = [NSUUID createUUID]; - selfUser.localMediumRemoteIdentifier = [NSUUID createUUID]; - - // then - XCTAssertTrue([p evaluateWithObject:self.user1]); - }]; -} - -- (void)testThatItFiltersSelfUserWithALocalImageAndTheSameRemoteImage -{ - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSPredicate *p = [ZMUser predicateForMediumImageDownloadFilter]; - - // when - ZMUser *selfUser = [ZMUser selfUserInContext:self.syncMOC]; - selfUser.remoteIdentifier = [NSUUID createUUID]; - selfUser.mediumRemoteIdentifier = [NSUUID createUUID]; - selfUser.localMediumRemoteIdentifier = selfUser.mediumRemoteIdentifier; - - // then - XCTAssertFalse([p evaluateWithObject:selfUser]); - }]; -} - -- (void)testThatItFiltersAUserWithALocalImageWhichExistsInTheCache -{ - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSPredicate *p = [ZMUser predicateForMediumImageDownloadFilter]; - - // when - self.user1.mediumRemoteIdentifier = [NSUUID createUUID]; - [self.user1 setImageMediumData:[@"data" dataUsingEncoding:NSUTF8StringEncoding]]; - - // then - XCTAssertFalse([p evaluateWithObject:self.user1]); - }]; -} - -- (void)testThatItDoesntFiltersAUserWithoutALocalImageInTheCache -{ - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSPredicate *p = [ZMUser predicateForMediumImageDownloadFilter]; - - // when - self.user1.mediumRemoteIdentifier = [NSUUID createUUID]; - - // then - XCTAssertTrue([p evaluateWithObject:self.user1]); - }]; -} - -@end - - - -@implementation ZMUserImageTranscoderTests (Requests) - -- (void)testThatItGeneratesARequestWhenTheMediumSelfUserImageIsMissing -{ - // given - NSUUID *imageID = [NSUUID createUUID]; - NSUUID *selfUserID = [NSUUID createUUID]; - __block ZMUser *selfUser = nil; - [self.syncMOC performGroupedBlockAndWait:^{ - selfUser = [ZMUser selfUserInContext:self.syncMOC]; - selfUser.remoteIdentifier = selfUserID; - selfUser.mediumRemoteIdentifier = imageID; - selfUser.localMediumRemoteIdentifier = nil; - selfUser.imageMediumData = nil; - selfUser.localSmallProfileRemoteIdentifier = nil; - selfUser.imageSmallProfileData = nil; - }]; - - // when - __block ZMTransportRequest *request; - [self.syncMOC performGroupedBlockAndWait:^{ - for (id t in self.sut.contextChangeTrackers) { - [t objectsDidChange:[NSSet setWithObject:selfUser]]; - } - request = [self.sut.requestGenerators nextRequest]; - }]; - WaitForAllGroupsToBeEmpty(1); - - // then - [self.syncMOC performGroupedBlockAndWait:^{ - XCTAssertNotNil(request); - NSString *query = [@"?conv_id=" stringByAppendingString:selfUserID.transportString]; - NSString *path = [NSString pathWithComponents:@[@"/assets", imageID.transportString]]; - path = [path stringByAppendingString:query]; - XCTAssertEqualObjects(request.path, path); - }]; -} - -- (void)testThatItGeneratesARequestWhenTheSmallSelfUserImageIsMissing -{ - // given - NSUUID *imageID = [NSUUID createUUID]; - NSUUID *selfUserID = [NSUUID createUUID]; - __block ZMUser *selfUser = nil; - [self.syncMOC performGroupedBlockAndWait:^{ - selfUser = [ZMUser selfUserInContext:self.syncMOC]; - selfUser.remoteIdentifier = selfUserID; - selfUser.mediumRemoteIdentifier = nil; - selfUser.localMediumRemoteIdentifier = nil; - selfUser.imageMediumData = nil; - selfUser.localSmallProfileRemoteIdentifier = nil; - selfUser.imageSmallProfileData = nil; - selfUser.smallProfileRemoteIdentifier = imageID; - }]; - - // when - __block ZMTransportRequest *request; - [self.syncMOC performGroupedBlockAndWait:^{ - for (id t in self.sut.contextChangeTrackers) { - [t objectsDidChange:[NSSet setWithObject:selfUser]]; - } - request = [self.sut.requestGenerators nextRequest]; - }]; - WaitForAllGroupsToBeEmpty(1); - - // then - [self.syncMOC performGroupedBlockAndWait:^{ - XCTAssertNotNil(request); - NSString *query = [@"?conv_id=" stringByAppendingString:selfUserID.transportString]; - NSString *path = [NSString pathWithComponents:@[@"/assets", imageID.transportString]]; - path = [path stringByAppendingString:query]; - XCTAssertEqualObjects(request.path, path); - }]; -} - -- (void)testThatItDoesntGeneratesARequestWhenAUserImageIsMissing -{ - // given - NSUUID *imageID = [NSUUID createUUID]; - [self.syncMOC performGroupedBlockAndWait:^{ - self.user1.mediumRemoteIdentifier = imageID; - self.user1.smallProfileRemoteIdentifier = imageID; - self.user1.localMediumRemoteIdentifier = nil; - self.user1.imageMediumData = nil; - self.user1.localSmallProfileRemoteIdentifier = nil; - self.user1.imageSmallProfileData = nil; - }]; - - // when - __block ZMTransportRequest *request; - [self.syncMOC performGroupedBlockAndWait:^{ - for (id t in self.sut.contextChangeTrackers) { - [t objectsDidChange:[NSSet setWithObject:self.user1]]; - } - request = [self.sut.requestGenerators nextRequest]; - }]; - WaitForAllGroupsToBeEmpty(1); - - // then - [self.syncMOC performGroupedBlockAndWait:^{ - XCTAssertNil(request); - }]; -} - -- (void)testThatItGeneratesARequestWhenAUserImageIsMissingAndItHasBeenRequested -{ - // given - NSUUID *imageID = [NSUUID createUUID]; - [self.syncMOC performGroupedBlockAndWait:^{ - self.user1.mediumRemoteIdentifier = imageID; - self.user1.localMediumRemoteIdentifier = nil; - self.user1.imageMediumData = nil; - self.user1.localSmallProfileRemoteIdentifier = nil; - self.user1.imageSmallProfileData = nil; - }]; - - // when - [ZMUserImageTranscoder requestAssetForUserWithObjectID:self.user1.objectID]; - - __block ZMTransportRequest *request; - [self.syncMOC performGroupedBlockAndWait:^{ - for (id t in self.sut.contextChangeTrackers) { - [t objectsDidChange:[NSSet setWithObject:self.user1]]; - } - request = [self.sut.requestGenerators nextRequest]; - }]; - WaitForAllGroupsToBeEmpty(1); - - // then - [self.syncMOC performGroupedBlockAndWait:^{ - XCTAssertNotNil(request); - NSString *query = [@"?conv_id=" stringByAppendingString:self.user1ID.transportString]; - NSString *path = [NSString pathWithComponents:@[@"/assets", imageID.transportString]]; - path = [path stringByAppendingString:query]; - XCTAssertEqualObjects(request.path, path); - }]; -} - - -- (void)testThatItGeneratesARequestForRetrievingASmallProfileUserImage; -{ - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSUUID *imageID = [NSUUID createUUID]; - self.user1.smallProfileRemoteIdentifier = imageID; - - // when - ZMTransportRequest *request = [self.sut requestForFetchingObject:self.user1 downstreamSync:self.sut.smallProfileDownstreamSync]; - - // then - XCTAssertEqual(request.method, ZMMethodGET); - NSString *query = [@"?conv_id=" stringByAppendingString:self.user1ID.transportString]; - NSString *path = [NSString pathWithComponents:@[@"/assets", imageID.transportString]]; - path = [path stringByAppendingString:query]; - XCTAssertEqualObjects(request.path, path); - XCTAssertEqual(request.acceptedResponseMediaTypes, ZMTransportAcceptImage); - XCTAssertNil(request.payload); - }]; -} - -- (void)testThatItGeneratesARequestForRetrievingAMediumUserImage; -{ - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSUUID *imageID = [NSUUID createUUID]; - self.user1.mediumRemoteIdentifier = imageID; - - // when - ZMTransportRequest *request = [self.sut requestForFetchingObject:self.user1 downstreamSync:self.sut.mediumDownstreamSync]; - - // then - XCTAssertEqual(request.method, ZMMethodGET); - NSString *query = [@"?conv_id=" stringByAppendingString:self.user1ID.transportString]; - NSString *path = [NSString pathWithComponents:@[@"/assets", imageID.transportString]]; - path = [path stringByAppendingString:query]; - XCTAssertEqualObjects(request.path, path); - XCTAssertEqual(request.acceptedResponseMediaTypes, ZMTransportAcceptImage); - XCTAssertNil(request.payload); - }]; -} - -- (void)testThatItUdpatesTheMediumImageFromAResponse; -{ - // TODO: There's a race condition here. - // If the mediumRemoteIdentifier changes while we're retrieving image data - // we'll still assume that the data is the newest. In order to fix that, - // we would have to change the ZMDownloadTranscoder protocol such - // that we can send the mediumRemoteIdentifier along the request and receive - // it back with the response. This is similar to how the upstream sync works. - - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSUUID *imageID = [NSUUID createUUID]; - self.user1.mediumRemoteIdentifier = imageID; - self.user1.imageMediumData = nil; - NSData *imageData = [self verySmallJPEGData]; - - ZMTransportResponse *response = [[ZMTransportResponse alloc] initWithImageData:imageData HTTPStatus:200 transportSessionError:nil headers:@{}]; - - // when - [self.sut updateObject:self.user1 withResponse:response downstreamSync:self.sut.mediumDownstreamSync]; - - // then - XCTAssertEqualObjects(self.user1.imageMediumData, imageData); - XCTAssertEqualObjects(self.user1.mediumRemoteIdentifier, imageID); - XCTAssertEqualObjects(self.user1.localMediumRemoteIdentifier, imageID); - }]; -} - - -- (void)testThatItUdpatesTheSmallProfileImageFromAResponse; -{ - // TODO: There's a race condition here. - // If the mediumRemoteIdentifier changes while we're retrieving image data - // we'll still assume that the data is the newest. In order to fix that, - // we would have to change the ZMDownloadTranscoder protocol such - // that we can send the mediumRemoteIdentifier along the request and receive - // it back with the response. This is similar to how the upstream sync works. - - [self.syncMOC performGroupedBlockAndWait:^{ - // given - NSUUID *imageID = [NSUUID createUUID]; - self.user1.smallProfileRemoteIdentifier = imageID; - self.user1.imageSmallProfileData = nil; - NSData *imageData = [self verySmallJPEGData]; - ZMTransportResponse *response = [[ZMTransportResponse alloc] initWithImageData:imageData HTTPStatus:200 transportSessionError:nil headers:@{}]; - - // when - [self.sut updateObject:self.user1 withResponse:response downstreamSync:self.sut.smallProfileDownstreamSync]; - - // then - XCTAssertEqualObjects(self.user1.imageSmallProfileData, imageData); - XCTAssertEqualObjects(self.user1.smallProfileRemoteIdentifier, imageID); - XCTAssertEqualObjects(self.user1.localSmallProfileRemoteIdentifier, imageID); - }]; -} - - -@end - - - - -@implementation ZMUserImageTranscoderTests (ImagePreprocessing) - - -- (void)testThatSmallProfileImageAndMediumImageDataGetsGeneratedForNewlyUpdatedSelfUser; -{ - - // given - __block ZMUser *selfUser; - [self.syncMOC performGroupedBlockAndWaitWithReasonableTimeout:^{ - selfUser = [ZMUser selfUserInContext:self.syncMOC]; - selfUser.originalProfileImageData = [self dataForResource:@"1900x1500" extension:@"jpg"]; - [self.sut.contextChangeTrackers[0] objectsDidChange:[NSSet setWithObject:selfUser]]; - - XCTAssertEqual(selfUser.imageSmallProfileData.length, 0U); - XCTAssertEqual(selfUser.imageMediumData.length, 0U); - }]; - - - // when - [self.syncMOC performBlockAndWait:^{ - (void) [self.sut.requestGenerators nextRequest]; - }]; - - // then - XCTAssert([self waitWithTimeout:0.5 forSaveOfContext:self.syncMOC untilBlock:^BOOL(){ - return selfUser.originalProfileImageData == nil; - }]); - - - [self.syncMOC performBlockAndWait:^{ - XCTAssertGreaterThan(selfUser.imageSmallProfileData.length, 0U); - XCTAssertGreaterThan(selfUser.imageMediumData.length, 0U); - XCTAssertNil(selfUser.originalProfileImageData); - }]; -} - -@end - - - -@implementation ZMUserImageTranscoderTests (ProfileImageUpload) - - -- (void)setUpSelfUser:(ZMUser **)selfUserP withRemoteID:(NSUUID *)remoteId formats:(NSArray *)formats locallyModifiedKeys:(NSArray *)locallyModifiedKeys -{ - // given - __block ZMUser *selfUser; - [self.syncMOC performGroupedBlockAndWaitWithReasonableTimeout:^{ - selfUser = [ZMUser selfUserInContext:self.syncMOC]; - selfUser.remoteIdentifier = remoteId; - selfUser.imageCorrelationIdentifier = [NSUUID createUUID]; - - for(NSNumber *boxedFormat in formats) { - ZMImageFormat format = (ZMImageFormat) boxedFormat.integerValue; - NSData *profileImageData; - - if (format == ZMImageFormatProfile) { - profileImageData = [self dataForResource:@"tiny" extension:@"jpg"]; - } - else if (format == ZMImageFormatMedium) { - profileImageData = [self dataForResource:@"medium" extension:@"jpg"]; - } - else { - XCTFail(@"Unrecognized image format in test"); - } - - [selfUser setImageData:profileImageData forFormat:format properties:nil]; - } - - ZMConversation *selfConv = [ZMConversation insertNewObjectInManagedObjectContext:self.syncMOC]; - selfConv.conversationType = ZMConversationTypeSelf; - selfConv.remoteIdentifier = remoteId; - - selfUser.needsToBeUpdatedFromBackend = NO; - [selfUser setLocallyModifiedKeys:[NSSet setWithArray:locallyModifiedKeys]]; - - }]; - - XCTAssertFalse(selfUser.needsToBeUpdatedFromBackend); - for (id tracker in self.sut.contextChangeTrackers) { - [tracker objectsDidChange:[NSSet setWithObject:selfUser]]; - } - - selfUser.originalProfileImageData = [self dataForResource:@"medium" extension:@"jpg"]; - *selfUserP = selfUser; -} - - -- (void)expectRequest:(ZMTransportRequest *)expectedRequest forSelfUser:(ZMUser *)selfUser format:(ZMImageFormat)format convID:(NSUUID *)conversationID handler:(void(^)(ZMTransportRequest *))handler -{ - __block ZMTransportRequest *request; - [self.syncMOC performGroupedBlockAndWait:^{ - id assetRequestFactoryMock = [OCMockObject mockForClass:ZMAssetRequestFactory.class]; - [[[[assetRequestFactoryMock expect] classMethod] - andReturn:expectedRequest] requestForImageOwner:selfUser - format:format - conversationID:conversationID - correlationID:OCMOCK_ANY - resultHandler:OCMOCK_ANY]; - - // when - request = [self.sut.requestGenerators nextRequest]; - - // finally - [assetRequestFactoryMock stopMocking]; - }]; - WaitForAllGroupsToBeEmpty(0.5); - [self.syncMOC performGroupedBlockAndWait:^{ - // then - handler(request); - }]; -} - -- (void)checkImageUploadWithFormat:(ZMImageFormat)format modifiedKeys:(NSArray *)locallyModifiedKeys failureRecorder:(ZMTFailureRecorder *)failureRecorder -{ - // given - ZMUser *selfUser; - NSUUID *selfUserAndSelfConversationID = [NSUUID createUUID]; - [self setUpSelfUser:&selfUser withRemoteID:selfUserAndSelfConversationID formats:@[@(format)] locallyModifiedKeys:locallyModifiedKeys]; - - // expect - ZMTransportRequest *expectedRequest = [ZMTransportRequest requestGetFromPath:@"/TEST-SUCCESSFUL"]; - - // then - [self expectRequest:expectedRequest forSelfUser:selfUser format:format convID:selfUserAndSelfConversationID handler:^(ZMTransportRequest *request) { - FHAssertTrue(failureRecorder, request != nil); - FHAssertEqualObjects(failureRecorder, request, expectedRequest); - }]; - -} - - -- (void)testThatItUploadsProfileImageDataToSelfConversation -{ - [self checkImageUploadWithFormat:ZMImageFormatProfile modifiedKeys:@[@"imageSmallProfileData"] failureRecorder:NewFailureRecorder()]; -} - - -- (void)testThatItUploadsMediumImageDataToSelfConversation -{ - [self checkImageUploadWithFormat:ZMImageFormatMedium modifiedKeys:@[@"imageMediumData"] failureRecorder:NewFailureRecorder()]; -} - - -- (void)testThatItSetsTheRemoteIdentifierForSmallProfile -{ - // given - ZMUser *selfUser; - NSUUID *selfUserAndSelfConversationID = [NSUUID createUUID]; - [self setUpSelfUser:&selfUser - withRemoteID:selfUserAndSelfConversationID - formats:@[@(ZMImageFormatProfile)] - locallyModifiedKeys:@[@"imageSmallProfileData"]]; - - NSUUID *imageID = [NSUUID createUUID]; - NSDictionary *smallProfileAsset = [ZMAssetMetaDataEncoder createAssetDataWithID:imageID imageOwner:selfUser format:ZMImageFormatProfile correlationID:selfUser.imageCorrelationIdentifier]; - - // expect - ZMTransportRequest *expectedSmallProfileRequest = [ZMTransportRequest requestGetFromPath:@"/small-profile-upload-request"]; - ZMTransportResponse *smallProfileResponse = [ZMTransportResponse responseWithPayload:@{@"data": smallProfileAsset} HTTPStatus:200 transportSessionError:nil]; - [self expectRequest:expectedSmallProfileRequest forSelfUser:selfUser format:ZMImageFormatProfile convID:selfUserAndSelfConversationID handler:^(ZMTransportRequest *request) { - XCTAssertNotNil(request); - XCTAssertEqual(request, expectedSmallProfileRequest); - - [request completeWithResponse:smallProfileResponse]; - }]; - - // then - [self.syncMOC performBlockAndWait:^{ - XCTAssertEqualObjects(selfUser.smallProfileRemoteIdentifier, imageID); - XCTAssertEqualObjects(selfUser.localSmallProfileRemoteIdentifier, imageID); - XCTAssertFalse([selfUser.keysThatHaveLocalModifications containsObject:@"smallProfileRemoteIdentifier_data"]); - XCTAssertFalse([selfUser.keysThatHaveLocalModifications containsObject:@"imageSmallProfileData"]); - }]; -} - - - -- (void)testThatItSetsTheRemoteIdentifierForMedium -{ - // given - ZMUser *selfUser; - NSUUID *selfUserAndSelfConversationID = [NSUUID createUUID]; - [self setUpSelfUser:&selfUser - withRemoteID:selfUserAndSelfConversationID - formats:@[@(ZMImageFormatMedium)] - locallyModifiedKeys:@[@"imageMediumData"]]; - - NSUUID *imageID = [NSUUID createUUID]; - NSDictionary *mediumAsset = [ZMAssetMetaDataEncoder createAssetDataWithID:imageID - imageOwner:selfUser - format:ZMImageFormatMedium - correlationID:selfUser.imageCorrelationIdentifier]; - - // expect - ZMTransportRequest *expectedMediumRequest = [ZMTransportRequest requestGetFromPath:@"/medium-upload-request"]; - ZMTransportResponse *mediumResponse = [ZMTransportResponse responseWithPayload:@{@"data": mediumAsset} HTTPStatus:200 transportSessionError:nil]; - [self expectRequest:expectedMediumRequest forSelfUser:selfUser format:ZMImageFormatMedium convID:selfUserAndSelfConversationID handler:^(ZMTransportRequest *request) { - XCTAssertNotNil(request); - XCTAssertEqual(request, expectedMediumRequest); - - [request completeWithResponse:mediumResponse]; - }]; - - // then - [self.syncMOC performBlockAndWait:^{ - XCTAssertEqualObjects(selfUser.mediumRemoteIdentifier, imageID); - XCTAssertEqualObjects(selfUser.localMediumRemoteIdentifier, imageID); - XCTAssertFalse([selfUser.keysThatHaveLocalModifications containsObject:@"mediumRemoteIdentifier_data"]); - XCTAssertFalse([selfUser.keysThatHaveLocalModifications containsObject:@"imageMediumData"]); - }]; - -} - - -- (void)testThatItRecoverFromInconsistenUserImageState -{ - // given - NSSet *modifiedKeys = [NSSet setWithArray:@[@"imageMediumData", @"imageSmallProfileData"]]; - ZMUser *selfUser; - NSUUID *selfUserAndSelfConversationID = [NSUUID createUUID]; - [self setUpSelfUser:&selfUser - withRemoteID:selfUserAndSelfConversationID - formats:@[@(ZMImageFormatProfile), @(ZMImageFormatMedium)] - locallyModifiedKeys:modifiedKeys.allObjects]; - - WaitForAllGroupsToBeEmpty(0.5); - - selfUser.imageMediumData = nil; - selfUser.imageSmallProfileData = nil; - - XCTAssertTrue([selfUser hasLocalModificationsForKeys:modifiedKeys]); - XCTAssertNil(selfUser.imageSmallProfileData); - XCTAssertNil(selfUser.imageMediumData); - - // when - ZMUserImageTranscoder __block *localSUT = [[ZMUserImageTranscoder alloc] initWithManagedObjectContext:self.syncMOC - imageProcessingQueue:self.queue]; - - XCTAssertTrue([self waitForAllGroupsToBeEmptyWithTimeout:0.5]); - - // then - [self.syncMOC performGroupedBlock:^{ - XCTAssertFalse([selfUser hasLocalModificationsForKeys:modifiedKeys]); - [localSUT tearDown]; - localSUT = nil; - }]; - - WaitForAllGroupsToBeEmpty(0.5); -} - -- (void)testThatItDoesNotUpdateTheImageIfTheCorrelationIDFromTheBackendResponseDiffersFromTheUserImageCorrelationID -{ - // given - ZMUser *selfUser; - NSUUID *selfUserAndSelfConversationID = [NSUUID createUUID]; - [self setUpSelfUser:&selfUser - withRemoteID:selfUserAndSelfConversationID - formats:@[@(ZMImageFormatProfile), @(ZMImageFormatMedium)] - locallyModifiedKeys:@[@"imageMediumData", @"imageSmallProfileData"]]; - - NSUUID *originalImageCorrelationIdentifier = selfUser.imageCorrelationIdentifier; - - NSUUID *imageID = [NSUUID createUUID]; - NSUUID *invalidCorrelationID = [NSUUID createUUID]; - NSDictionary *smallProfileAsset = [ZMAssetMetaDataEncoder createAssetDataWithID:imageID - imageOwner:selfUser - format:ZMImageFormatProfile - correlationID:invalidCorrelationID]; - - // expect - ZMTransportRequest *expectedSmallProfileRequest = [ZMTransportRequest requestGetFromPath:@"/upload-request"]; - ZMTransportResponse *response = [ZMTransportResponse responseWithPayload:@{@"data": smallProfileAsset, - @"id" : NSUUID.createUUID.transportString} - HTTPStatus:200 - transportSessionError:nil]; - - [self expectRequest:expectedSmallProfileRequest - forSelfUser:selfUser - format:ZMImageFormatProfile - convID:selfUserAndSelfConversationID - handler:^(ZMTransportRequest *request) { - - XCTAssertNotNil(request); - XCTAssertEqual(request, expectedSmallProfileRequest); - - [request completeWithResponse:response]; - }]; - - // then - [self.syncMOC performBlockAndWait:^{ - XCTAssertEqualObjects(selfUser.imageCorrelationIdentifier, originalImageCorrelationIdentifier); - XCTAssertNil(selfUser.smallProfileRemoteIdentifier); - XCTAssertFalse([selfUser.keysThatHaveLocalModifications containsObject:@"mediumRemoteIdentifier_data"]); - }]; - -} - -- (void)testThatItMarksImageIdentifiersAsToBeUploadedAfterUploadingSmallProfileWhenBothImagesAreUploaded -{ - // given - ZMUser *selfUser; - NSUUID *selfUserAndSelfConversationID = [NSUUID createUUID]; - [self setUpSelfUser:&selfUser - withRemoteID:selfUserAndSelfConversationID - formats:@[@(ZMImageFormatProfile)] - locallyModifiedKeys:@[@"imageSmallProfileData"]]; - - NSUUID *imageID = [NSUUID createUUID]; - NSDictionary *smallProfileAsset = [ZMAssetMetaDataEncoder createAssetDataWithID:imageID imageOwner:selfUser format:ZMImageFormatProfile correlationID:selfUser.imageCorrelationIdentifier]; - [selfUser processingDidFinish]; - - // expect - ZMTransportRequest *expectedSmallProfileRequest = [ZMTransportRequest requestGetFromPath:@"/small-profile-upload-request"]; - ZMTransportResponse *smallProfileResponse = [ZMTransportResponse responseWithPayload:@{@"data": smallProfileAsset} HTTPStatus:200 transportSessionError:nil]; - [self expectRequest:expectedSmallProfileRequest forSelfUser:selfUser format:ZMImageFormatProfile convID:selfUserAndSelfConversationID handler:^(ZMTransportRequest *request) { - XCTAssertNotNil(request); - - [request completeWithResponse:smallProfileResponse]; - }]; - - // then - [self.syncMOC performBlockAndWait:^{ - XCTAssertEqualObjects(selfUser.smallProfileRemoteIdentifier, imageID); - XCTAssertEqualObjects(selfUser.localSmallProfileRemoteIdentifier, imageID); - XCTAssertTrue([selfUser.keysThatHaveLocalModifications containsObject:@"smallProfileRemoteIdentifier_data"]); - XCTAssertTrue([selfUser.keysThatHaveLocalModifications containsObject:@"mediumRemoteIdentifier_data"]); - XCTAssertFalse([selfUser.keysThatHaveLocalModifications containsObject:@"imageSmallProfileData"]); - }]; -} - -@end diff --git a/wire-ios-sync-engine/Tests/Source/Synchronization/ZMConversationAccessModeTests.swift b/wire-ios-sync-engine/Tests/Source/Synchronization/ZMConversationAccessModeTests.swift index ba7436982b3..549cac9e0f5 100644 --- a/wire-ios-sync-engine/Tests/Source/Synchronization/ZMConversationAccessModeTests.swift +++ b/wire-ios-sync-engine/Tests/Source/Synchronization/ZMConversationAccessModeTests.swift @@ -111,7 +111,7 @@ public class ZMConversationAccessModeTests: MessagingTest { switch apiVersion { case .v0: XCTAssertEqual(request.path, "/conversations/\(conversation.remoteIdentifier!.transportString())/access") - case .v1, .v2, .v3, .v4, .v5, .v6: + case .v1, .v2, .v3, .v4, .v5, .v6, .v7: XCTAssertEqual( request.path, "/v\(apiVersion.rawValue)/conversations/\(conversation.remoteIdentifier!.transportString())/access" diff --git a/wire-ios-sync-engine/Tests/Source/UserSession/AddressBookSearchTests.swift b/wire-ios-sync-engine/Tests/Source/UserSession/AddressBookSearchTests.swift deleted file mode 100644 index 848d525df0f..00000000000 --- a/wire-ios-sync-engine/Tests/Source/UserSession/AddressBookSearchTests.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Foundation -import XCTest -@testable import WireSyncEngine - -class AddressBookSearchTests: MessagingTest { - - var sut: WireSyncEngine.AddressBookSearch! - var addressBook: MockAddressBook! - - override func setUp() { - super.setUp() - addressBook = MockAddressBook() - sut = WireSyncEngine.AddressBookSearch(addressBook: addressBook) - } - - override func tearDown() { - sut = nil - addressBook = nil - super.tearDown() - } -} - -// MARK: - Search query - -extension AddressBookSearchTests { - - func testThatItSearchesByNameWithMatch() { - - // given - addressBook.contacts = [ - MockAddressBookContact(firstName: "Olivia", emailAddresses: ["oli@example.com"], phoneNumbers: []), - MockAddressBookContact(firstName: "Ada", emailAddresses: [], phoneNumbers: ["+155505012"]) - ] - - // when - let result = Array(sut.contactsMatchingQuery("ivi", identifiersToExclude: [])) - - // then - XCTAssertEqual(result.count, 1) - guard result.count == 1 else { return } - XCTAssertEqual(result[0].emailAddresses, ["oli@example.com"]) - } - - func testThatItSearchesByNameWithMatchExcludingIdentifiers() { - - // given - let identifier = "233124" - addressBook.contacts = [ - MockAddressBookContact( - firstName: "Olivia 1", - emailAddresses: ["oli@example.com"], - phoneNumbers: [], - identifier: identifier - ), - MockAddressBookContact(firstName: "Olivia 2", emailAddresses: [], phoneNumbers: ["+155505012"]) - ] - - // when - let result = Array(sut.contactsMatchingQuery("ivi", identifiersToExclude: [identifier])) - - // then - XCTAssertEqual(result.count, 1) - guard result.count == 1 else { return } - XCTAssertEqual(result[0].firstName, "Olivia 2") - } - - func testThatItSearchesByNameWithNoMatch() { - - // given - addressBook.contacts = [ - MockAddressBookContact(firstName: "Olivia", emailAddresses: ["oli@example.com"], phoneNumbers: []), - MockAddressBookContact(firstName: "Ada", emailAddresses: [], phoneNumbers: ["+155505012"]) - ] - - // when - let result = Array(sut.contactsMatchingQuery("Nadia", identifiersToExclude: [])) - - // then - XCTAssertEqual(result.count, 0) - } -} diff --git a/wire-ios-sync-engine/Tests/Source/UserSession/SearchTaskTests.swift b/wire-ios-sync-engine/Tests/Source/UserSession/SearchTaskTests.swift index 1a6dd20c289..20c6792ec86 100644 --- a/wire-ios-sync-engine/Tests/Source/UserSession/SearchTaskTests.swift +++ b/wire-ios-sync-engine/Tests/Source/UserSession/SearchTaskTests.swift @@ -58,10 +58,11 @@ final class SearchTaskTests: DatabaseTest { super.tearDown() } - func createConnectedUser(withName name: String) -> ZMUser { + func createConnectedUser(withName name: String, domain: String? = nil) -> ZMUser { let user = ZMUser.insertNewObject(in: uiMOC) user.name = name user.remoteIdentifier = UUID.create() + user.domain = domain let connection = ZMConnection.insertNewObject(in: uiMOC) connection.to = user @@ -330,6 +331,63 @@ final class SearchTaskTests: DatabaseTest { XCTAssertTrue(waitForCustomExpectations(withTimeout: 0.5)) } + func testThatItDoesNotFindUsersWithOtherDomainsIfSearchDomainIsRequired() { + // given + let resultArrived = customExpectation(description: "received result") + let user = createConnectedUser(withName: "userA", domain: "bella.com") + + let request = SearchRequest(query: "userA@bella.com", searchDomain: "anta.com", searchOptions: [.contacts]) + let task = makeSearchTask(request: request) + + // expect + task.addResultHandler { result, _ in + resultArrived.fulfill() + XCTAssertEqual(result.contacts.count, 0) + } + + // when + task.performLocalSearch() + XCTAssertTrue(waitForCustomExpectations(withTimeout: 0.5)) + } + + func testThatItFindsUsersWithSameDomainAsSelfUser() { + // given + let resultArrived = customExpectation(description: "received result") + let user = createConnectedUser(withName: "userA", domain: "anta.com") + + let request = SearchRequest(query: "userA@anta.com", searchOptions: [.contacts]) + let task = makeSearchTask(request: request) + + // expect + task.addResultHandler { result, _ in + resultArrived.fulfill() + XCTAssertTrue(result.contacts.compactMap(\.user).contains(user)) + } + + // when + task.performLocalSearch() + XCTAssertTrue(waitForCustomExpectations(withTimeout: 0.5)) + } + + func testThatItFindsUsersWithOtherDomainsIfSearchDomainIsNotRequired() { + // given + let resultArrived = customExpectation(description: "received result") + let user = createConnectedUser(withName: "userA", domain: "bella.com") + + let request = SearchRequest(query: "userA@bella.com", searchOptions: [.contacts]) + let task = makeSearchTask(request: request) + + // expect + task.addResultHandler { result, _ in + resultArrived.fulfill() + XCTAssertTrue(result.contacts.compactMap(\.user).contains(user)) + } + + // when + task.performLocalSearch() + XCTAssertTrue(waitForCustomExpectations(withTimeout: 0.5)) + } + // MARK: Team member local search func testThatItCanSearchForTeamMembersLocally() { diff --git a/wire-ios-sync-engine/Tests/Source/UserSession/SyncStatusTests.swift b/wire-ios-sync-engine/Tests/Source/UserSession/SyncStatusTests.swift index 239a3ef436b..94b5d51297e 100644 --- a/wire-ios-sync-engine/Tests/Source/UserSession/SyncStatusTests.swift +++ b/wire-ios-sync-engine/Tests/Source/UserSession/SyncStatusTests.swift @@ -134,6 +134,8 @@ final class SyncStatusTests: MessagingTest { // when sut.finishCurrentSyncPhase(phase: .fetchingFeatureConfig) // when + sut.finishCurrentSyncPhase(phase: .fetchingBackendMLSPublicKeys) + // when sut.finishCurrentSyncPhase(phase: .updateSelfSupportedProtocols) // when sut.finishCurrentSyncPhase(phase: .evaluate1on1ConversationsForMLS) @@ -229,6 +231,8 @@ final class SyncStatusTests: MessagingTest { // when sut.finishCurrentSyncPhase(phase: .fetchingFeatureConfig) // when + sut.finishCurrentSyncPhase(phase: .fetchingBackendMLSPublicKeys) + // when sut.finishCurrentSyncPhase(phase: .updateSelfSupportedProtocols) // when sut.finishCurrentSyncPhase(phase: .evaluate1on1ConversationsForMLS) @@ -516,6 +520,9 @@ final class SyncStatusTests: MessagingTest { XCTAssertEqual(sut.currentSyncPhase, .fetchingFeatureConfig) sut.finishCurrentSyncPhase(phase: .fetchingFeatureConfig) // when + XCTAssertEqual(sut.currentSyncPhase, .fetchingBackendMLSPublicKeys) + sut.finishCurrentSyncPhase(phase: .fetchingBackendMLSPublicKeys) + // when XCTAssertEqual(sut.currentSyncPhase, .updateSelfSupportedProtocols) sut.finishCurrentSyncPhase(phase: .updateSelfSupportedProtocols) // when diff --git a/wire-ios-sync-engine/Tests/Source/UserSession/UnauthenticatedSessionTests+DomainLookup.swift b/wire-ios-sync-engine/Tests/Source/UserSession/UnauthenticatedSessionTests+DomainLookup.swift index 914bb0c9073..cb9659cd013 100644 --- a/wire-ios-sync-engine/Tests/Source/UserSession/UnauthenticatedSessionTests+DomainLookup.swift +++ b/wire-ios-sync-engine/Tests/Source/UserSession/UnauthenticatedSessionTests+DomainLookup.swift @@ -68,7 +68,7 @@ public final class UnauthenticatedSessionTests_DomainLookup: ZMTBaseTest { // then XCTAssertNotNil(transportSession.lastEnqueuedRequest) XCTAssertEqual(transportSession.lastEnqueuedRequest?.path, "/custom-backend/by-domain/example.com") - XCTAssertEqual(transportSession.lastEnqueuedRequest?.method, ZMTransportRequestMethod.get) + XCTAssertEqual(transportSession.lastEnqueuedRequest?.method, .get) } func testThatItURLEncodeRequest() { @@ -81,7 +81,7 @@ public final class UnauthenticatedSessionTests_DomainLookup: ZMTBaseTest { // then XCTAssertNotNil(transportSession.lastEnqueuedRequest) XCTAssertEqual(transportSession.lastEnqueuedRequest?.path, "/custom-backend/by-domain/example%20com") - XCTAssertEqual(transportSession.lastEnqueuedRequest?.method, ZMTransportRequestMethod.get) + XCTAssertEqual(transportSession.lastEnqueuedRequest?.method, .get) } func testThatItLookupReturnsNoAPiVersionError() { diff --git a/wire-ios-sync-engine/Tests/Source/UserSession/UnauthenticatedSessionTests+SSO.swift b/wire-ios-sync-engine/Tests/Source/UserSession/UnauthenticatedSessionTests+SSO.swift index 72d6dc49b08..143017a895e 100644 --- a/wire-ios-sync-engine/Tests/Source/UserSession/UnauthenticatedSessionTests+SSO.swift +++ b/wire-ios-sync-engine/Tests/Source/UserSession/UnauthenticatedSessionTests+SSO.swift @@ -64,7 +64,7 @@ final class UnauthenticatedSessionTests_SSO: ZMTBaseTest { // then XCTAssertNotNil(transportSession.lastEnqueuedRequest) XCTAssertEqual(transportSession.lastEnqueuedRequest?.path, "/sso/settings") - XCTAssertEqual(transportSession.lastEnqueuedRequest?.method, ZMTransportRequestMethod.get) + XCTAssertEqual(transportSession.lastEnqueuedRequest?.method, .get) } // MARK: Response handling diff --git a/wire-ios-sync-engine/Tests/Source/UserSession/ZMClientRegistrationStatusTests.swift b/wire-ios-sync-engine/Tests/Source/UserSession/ZMClientRegistrationStatusTests.swift index d88ad7c7654..04e82af0f23 100644 --- a/wire-ios-sync-engine/Tests/Source/UserSession/ZMClientRegistrationStatusTests.swift +++ b/wire-ios-sync-engine/Tests/Source/UserSession/ZMClientRegistrationStatusTests.swift @@ -583,7 +583,7 @@ final class ZMClientRegistrationStatusTests: MessagingTest { enableMLS() // then - XCTAssertFalse(sut.needsToRegisterMLSCLient) + XCTAssertFalse(sut.needsToRegisterMLSClient) } } @@ -600,7 +600,7 @@ final class ZMClientRegistrationStatusTests: MessagingTest { enableMLS() // then - XCTAssertTrue(sut.needsToRegisterMLSCLient) + XCTAssertTrue(sut.needsToRegisterMLSClient) } } @@ -616,7 +616,7 @@ final class ZMClientRegistrationStatusTests: MessagingTest { enableMLS() // then - XCTAssertFalse(sut.needsToRegisterMLSCLient) + XCTAssertFalse(sut.needsToRegisterMLSClient) } } @@ -625,11 +625,11 @@ final class ZMClientRegistrationStatusTests: MessagingTest { // given let selfUser = ZMUser.selfUser(in: syncMOC) selfUser.remoteIdentifier = UUID() - DeveloperFlag.enableMLSSupport.enable(false) + FeatureRepository(context: self.syncMOC).storeMLS(Feature.MLS(status: .disabled)) BackendInfo.apiVersion = .v5 // then - XCTAssertFalse(sut.needsToRegisterMLSCLient) + XCTAssertFalse(sut.needsToRegisterMLSClient) } } @@ -785,9 +785,9 @@ final class ZMClientRegistrationStatusTests: MessagingTest { @objc private func enableMLS() { - DeveloperFlag.storage = .temporary() - DeveloperFlag.enableMLSSupport.enable(true) + FeatureRepository(context: syncMOC).storeMLS(Feature.MLS(status: .enabled)) BackendInfo.apiVersion = .v5 + BackendInfo.isMLSEnabled = true } @objc diff --git a/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests+Authentication.swift b/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests+Authentication.swift index ad1de5ae82a..a6beb39e942 100644 --- a/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests+Authentication.swift +++ b/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests+Authentication.swift @@ -56,7 +56,7 @@ final class ZMUserSessionTests_Authentication: ZMUserSessionTestsBase { // then let request = try XCTUnwrap(transportSession.lastEnqueuedRequest) let payload = request.payload as? [String: Any] - XCTAssertEqual(request.method, ZMTransportRequestMethod.delete) + XCTAssertEqual(request.method, .delete) XCTAssertEqual(request.path, "/clients/\(selfClient.remoteIdentifier!)") XCTAssertEqual(payload?["password"] as? String, credentials.password) } @@ -73,7 +73,7 @@ final class ZMUserSessionTests_Authentication: ZMUserSessionTestsBase { // then let request = try XCTUnwrap(transportSession.lastEnqueuedRequest) let payload = request.payload as? [String: Any] - XCTAssertEqual(request.method, ZMTransportRequestMethod.delete) + XCTAssertEqual(request.method, .delete) XCTAssertEqual(request.path, "/clients/\(selfClient.remoteIdentifier!)") XCTAssertEqual(payload?.keys.count, 0) } diff --git a/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests.swift b/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests.swift index 4b4b4cb4c08..acc707a0be6 100644 --- a/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests.swift +++ b/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests.swift @@ -454,7 +454,10 @@ final class ZMUserSessionTests: ZMUserSessionTestsBase { func test_itPerformsPeriodicMLSUpdates_AfterQuickSync() { // GIVEN - DeveloperFlag.enableMLSSupport.enable(true, storage: .temporary()) + syncMOC.performAndWait { + let mls = Feature.MLS(status: .enabled, config: .init()) + self.sut.featureRepository.storeMLS(mls) + } mockMLSService.performPendingJoins_MockMethod = {} mockMLSService.commitPendingProposalsIfNeeded_MockMethod = {} mockMLSService.uploadKeyPackagesIfNeeded_MockMethod = {} diff --git a/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTestsBase.swift b/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTestsBase.swift index 2ed30c6f900..75edd0b0b63 100644 --- a/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTestsBase.swift +++ b/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTestsBase.swift @@ -126,6 +126,7 @@ class ZMUserSessionTestsBase: MessagingTest { var builder = ZMUserSessionBuilder() builder.withAllDependencies( + apiServiceFactory: { _, _ in MockAPIService() }, appVersion: "00000", application: application, cryptoboxMigrationManager: mockCryptoboxMigrationManager, diff --git a/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests_NetworkState.swift b/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests_NetworkState.swift index e248670b19d..d194e170db0 100644 --- a/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests_NetworkState.swift +++ b/wire-ios-sync-engine/Tests/Source/UserSession/ZMUserSessionTests_NetworkState.swift @@ -46,6 +46,7 @@ final class ZMUserSessionTests_NetworkState: ZMUserSessionTestsBase { var builder = ZMUserSessionBuilder() builder.withAllDependencies( + apiServiceFactory: { _, _ in MockAPIService() }, appVersion: "00000", application: application, cryptoboxMigrationManager: mockCryptoboxMigrationManager, diff --git a/wire-ios-sync-engine/WireSyncEngine.xcodeproj/project.pbxproj b/wire-ios-sync-engine/WireSyncEngine.xcodeproj/project.pbxproj index daf64fe2e46..309aa098f4a 100644 --- a/wire-ios-sync-engine/WireSyncEngine.xcodeproj/project.pbxproj +++ b/wire-ios-sync-engine/WireSyncEngine.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -14,6 +14,7 @@ 0155194E2C20D29400037358 /* WireDomainSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0155194D2C20D29400037358 /* WireDomainSupport.framework */; }; 017704E42CA4503C00BE69ED /* EnableAnalyticsUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017704D92CA44C0D00BE69ED /* EnableAnalyticsUseCaseTests.swift */; }; 017704E52CA4505A00BE69ED /* DisableAnalyticsUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017704DB2CA44C0D00BE69ED /* DisableAnalyticsUseCaseTests.swift */; }; + 017F32C92D03AC3000471B3D /* WireAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 017F32C82D03AC3000471B3D /* WireAPI */; }; 018A3DBC2B07998400EB3D6B /* GetUserClientFingerprintUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018A3DBB2B07998400EB3D6B /* GetUserClientFingerprintUseCase.swift */; }; 018A3DBF2B0799CA00EB3D6B /* GetUserClientFingerprintUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018A3DBE2B0799CA00EB3D6B /* GetUserClientFingerprintUseCaseTests.swift */; }; 018F17B92B83B70A00E0594D /* AVSConversationType+Conference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018F17B82B83B70A00E0594D /* AVSConversationType+Conference.swift */; }; @@ -23,6 +24,8 @@ 0601900B2678750D0043F8F8 /* DeepLinkURLActionProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 161ACB3E23F6E4C200ABFF33 /* DeepLinkURLActionProcessorTests.swift */; }; 060C06632B73DB8800B484C6 /* SnoozeCertificateEnrollmentUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060C06622B73DB8800B484C6 /* SnoozeCertificateEnrollmentUseCase.swift */; }; 060C06682B7619E300B484C6 /* SelfClientCertificateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060C06672B7619E300B484C6 /* SelfClientCertificateProvider.swift */; }; + 06185AA02CEBA634005D3CFE /* GetMLSFeatureUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06185A9F2CEBA632005D3CFE /* GetMLSFeatureUseCase.swift */; }; + 06185AA22CEBA6F6005D3CFE /* GetMLSFeatureUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06185AA12CEBA6F5005D3CFE /* GetMLSFeatureUseCaseTests.swift */; }; 061F791C2B767B3D00E8827B /* SelfClientCertificateProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061F791A2B767AE100E8827B /* SelfClientCertificateProviderTests.swift */; }; 061F791E2B76828500E8827B /* SnoozeCertificateEnrollmentUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061F791D2B76828500E8827B /* SnoozeCertificateEnrollmentUseCaseTests.swift */; }; 06239126274DB73A0065A72D /* StartLoginURLActionProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06239125274DB73A0065A72D /* StartLoginURLActionProcessor.swift */; }; @@ -50,12 +53,13 @@ 06E0979C2A261E5D00B38C4A /* ZMUserSession+RecurringAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E0979B2A261E5D00B38C4A /* ZMUserSession+RecurringAction.swift */; }; 06E097A02A264F0300B38C4A /* ZMUserSessionTests+RecurringActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E0979F2A264F0300B38C4A /* ZMUserSessionTests+RecurringActions.swift */; }; 06F98D602437916B007E914A /* SignatureRequestStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F98D5E24379143007E914A /* SignatureRequestStrategyTests.swift */; }; + 06FBF22F2CFD850700DA13EC /* UserSession+Federation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FBF22E2CFD84FC00DA13EC /* UserSession+Federation.swift */; }; + 06FC48082CF0E36A00D31461 /* SearchUsersUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FC48072CF0E36A00D31461 /* SearchUsersUseCase.swift */; }; 092083401BA95EE100F82B29 /* UserClientRequestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0920833F1BA95EE100F82B29 /* UserClientRequestFactory.swift */; }; 0920C4DA1B305FF500C55728 /* UserSessionGiphyRequestStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0920C4D81B305FF500C55728 /* UserSessionGiphyRequestStateTests.swift */; }; 093694451BA9633300F36B3A /* UserClientRequestFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 093694441BA9633300F36B3A /* UserClientRequestFactoryTests.swift */; }; 09531F161AE960E300B8556A /* ZMLoginCodeRequestTranscoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 09531F131AE960E300B8556A /* ZMLoginCodeRequestTranscoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 09531F181AE960E300B8556A /* ZMLoginCodeRequestTranscoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 09531F141AE960E300B8556A /* ZMLoginCodeRequestTranscoder.m */; }; - 09531F1C1AE9644800B8556A /* ZMLoginCodeRequestTranscoderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 09531F1A1AE9644800B8556A /* ZMLoginCodeRequestTranscoderTests.m */; }; 09914E531BD6613D00C10BF8 /* ZMDecodedAPSMessageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 09914E521BD6613D00C10BF8 /* ZMDecodedAPSMessageTest.m */; }; 09B730961B3045E400A5CCC9 /* ProxiedRequestStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B730941B3045E400A5CCC9 /* ProxiedRequestStatusTests.swift */; }; 09BA924C1BD55FA5000DC962 /* UserClientRequestStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0920833C1BA84F3100F82B29 /* UserClientRequestStrategyTests.swift */; }; @@ -144,7 +148,6 @@ 168474262252579A004DE9EC /* ZMUserSessionTests+Syncing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168474252252579A004DE9EC /* ZMUserSessionTests+Syncing.swift */; }; 16849B1C23EDA1FD00C025A8 /* MockUpdateEventProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16849B1B23EDA1FD00C025A8 /* MockUpdateEventProcessor.swift */; }; 16849B2023EDB32B00C025A8 /* MockSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16849B1F23EDB32B00C025A8 /* MockSessionManager.swift */; }; - 168C0B3F1E97CE3900315044 /* ZMLastUpdateEventIDTranscoderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54188DCA19D19DE200DA40E4 /* ZMLastUpdateEventIDTranscoderTests.m */; }; 168CF42720077C54009FCB89 /* TeamInvitationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168CF42620077C54009FCB89 /* TeamInvitationStatus.swift */; }; 168CF4292007840A009FCB89 /* Team+Invite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168CF4282007840A009FCB89 /* Team+Invite.swift */; }; 168CF42B20079A02009FCB89 /* TeamInvitationRequestStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 168CF42A20079A02009FCB89 /* TeamInvitationRequestStrategy.swift */; }; @@ -182,7 +185,6 @@ 16ED865D23E30F7E00CB1766 /* ZMUserSesson+Proxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16ED865C23E30F7E00CB1766 /* ZMUserSesson+Proxy.swift */; }; 16ED865F23E3145C00CB1766 /* ZMUserSession+Clients.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16ED865E23E3145C00CB1766 /* ZMUserSession+Clients.swift */; }; 16F5F16C1E4092C00062F0AE /* NSManagedObjectContext+CTCallCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16F5F16B1E4092C00062F0AE /* NSManagedObjectContext+CTCallCenter.swift */; }; - 16F6BB381EDEA659009EA803 /* SearchResult+AddressBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16F6BB371EDEA659009EA803 /* SearchResult+AddressBook.swift */; }; 16FF474C1F0D54B20044C491 /* SessionManager+Logs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16FF474B1F0D54B20044C491 /* SessionManager+Logs.swift */; }; 1836188BC0E48C1AC1671FC2 /* ZMSyncStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D6B0837E10BD4D5E88805E3 /* ZMSyncStrategyTests.swift */; }; 259AAB0D2B9F477F00B13A7C /* LastE2EIdentityUpdateDateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259AAB0C2B9F477F00B13A7C /* LastE2EIdentityUpdateDateProtocol.swift */; }; @@ -208,7 +210,6 @@ 54131BCE25C7FFCA00CE2CA2 /* SessionManager+AuthenticationStatusDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54131BCD25C7FFCA00CE2CA2 /* SessionManager+AuthenticationStatusDelegate.swift */; }; 54257C081DF1C94200107FE7 /* TopConversationsDirectory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54257C071DF1C94200107FE7 /* TopConversationsDirectory.swift */; }; 542DFEE61DDCA452000F5B95 /* UserProfileUpdateStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542DFEE51DDCA452000F5B95 /* UserProfileUpdateStatusTests.swift */; }; - 542DFEE81DDCA4FD000F5B95 /* UserProfileUpdateRequestStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542DFEE71DDCA4FD000F5B95 /* UserProfileUpdateRequestStrategyTests.swift */; }; 543095931DE76B170065367F /* random1.txt in Resources */ = {isa = PBXBuildFile; fileRef = 543095921DE76B170065367F /* random1.txt */; }; 543095951DE76B270065367F /* random2.txt in Resources */ = {isa = PBXBuildFile; fileRef = 543095941DE76B270065367F /* random2.txt */; }; 5430E9251BAA0D9F00395E05 /* WireSyncEngineLogs.h in Headers */ = {isa = PBXBuildFile; fileRef = 5430E9231BAA0D9F00395E05 /* WireSyncEngineLogs.h */; }; @@ -217,10 +218,8 @@ 544BA1271A433DE400D3B852 /* NSError+ZMUserSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 3E05F247192A4F8900F22D80 /* NSError+ZMUserSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; 544BA1571A43424F00D3B852 /* WireSyncEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549815931A43232400A7CE2E /* WireSyncEngine.framework */; }; 544F8FF31DDCD34600D1AB04 /* UserProfileUpdateNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544F8FF21DDCD34600D1AB04 /* UserProfileUpdateNotifications.swift */; }; - 545434AC19AB6ADA003892D9 /* ZMSelfTranscoderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54F8D73119AB677400146664 /* ZMSelfTranscoderTests.m */; }; 5458273E2541C3A9002B8F83 /* PresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5458273D2541C3A9002B8F83 /* PresentationDelegate.swift */; }; 5458AF841F7021B800E45977 /* PreLoginAuthenticationNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5458AF831F7021B800E45977 /* PreLoginAuthenticationNotification.swift */; }; - 545F601C1D6C336D00C2C55B /* AddressBookSearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545F601B1D6C336D00C2C55B /* AddressBookSearchTests.swift */; }; 545FC3341A5B003A005EEA26 /* ObjectTranscoderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54F8D74919AB67B300146664 /* ObjectTranscoderTests.m */; }; 546392721D79D5210094EC66 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546392711D79D5210094EC66 /* Application.swift */; }; 5463C897193F3C74006799DE /* ZMTimingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5463C890193F38A6006799DE /* ZMTimingTests.m */; }; @@ -238,7 +237,6 @@ 547E5B581DDB4B800038D936 /* UserProfileUpdateStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547E5B571DDB4B800038D936 /* UserProfileUpdateStatus.swift */; }; 547E5B5A1DDB67390038D936 /* UserProfileUpdateRequestStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547E5B591DDB67390038D936 /* UserProfileUpdateRequestStrategy.swift */; }; 54880E3D194B5845007271AA /* ZMOperationLoopTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 85D858D72B109C5D9A85645B /* ZMOperationLoopTests.m */; }; - 549552541D645683004F21F6 /* AddressBookTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549552511D64567C004F21F6 /* AddressBookTests.swift */; }; 549710081F6FF5C100026EDD /* NotificationInContext+UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549710071F6FF5C100026EDD /* NotificationInContext+UserSession.swift */; }; 5497100A1F6FFE9900026EDD /* ClientUpdateNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549710091F6FFE9900026EDD /* ClientUpdateNotification.swift */; }; 54973A361DD48CAB007F8702 /* NSManagedObject+CryptoStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54973A351DD48CAB007F8702 /* NSManagedObject+CryptoStack.swift */; }; @@ -251,19 +249,15 @@ 549816351A432BC800A7CE2E /* ZMLoginTranscoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 54C11B9F19D1E4A100576A96 /* ZMLoginTranscoder.m */; }; 549816451A432BC800A7CE2E /* ZMOperationLoop.m in Sources */ = {isa = PBXBuildFile; fileRef = 85D8502FFC4412F91D0CC1A4 /* ZMOperationLoop.m */; }; 549816471A432BC800A7CE2E /* ZMSyncStrategy.m in Sources */ = {isa = PBXBuildFile; fileRef = 85D859D47B6EBF09E4137658 /* ZMSyncStrategy.m */; }; - 54991D581DEDCF2B007E282F /* AddressBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54991D571DEDCF2B007E282F /* AddressBook.swift */; }; - 54991D5A1DEDD07E007E282F /* ContactAddressBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54991D591DEDD07E007E282F /* ContactAddressBook.swift */; }; 54A0A6311BCE9864001A3A4C /* ZMHotFix.m in Sources */ = {isa = PBXBuildFile; fileRef = F95ECF4D1B94A553009F91BA /* ZMHotFix.m */; }; 54A0A6321BCE9867001A3A4C /* ZMHotFixDirectory.m in Sources */ = {isa = PBXBuildFile; fileRef = 54DE26B21BC56E62002B5FBC /* ZMHotFixDirectory.m */; }; 54A170651B300696001B41A5 /* ProxiedRequestStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A170631B300696001B41A5 /* ProxiedRequestStrategy.swift */; }; 54A170691B300717001B41A5 /* ProxiedRequestStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A170671B300717001B41A5 /* ProxiedRequestStrategyTests.swift */; }; 54A227D61D6604A5009414C0 /* SynchronizationMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A227D51D6604A5009414C0 /* SynchronizationMocks.swift */; }; - 54A343471D6B589A004B65EA /* AddressBookSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A343461D6B589A004B65EA /* AddressBookSearch.swift */; }; 54A3F24F1C08523500FE3A6B /* ZMOperationLoop.h in Headers */ = {isa = PBXBuildFile; fileRef = 85D85F3EC8565FD102AC0E5B /* ZMOperationLoop.h */; settings = {ATTRIBUTES = (Public, ); }; }; 54AB428E1DF5C5B400381F2C /* TopConversationsDirectoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AB428D1DF5C5B400381F2C /* TopConversationsDirectoryTests.swift */; }; 54BFDF681BDA6F9A0034A3DB /* HistorySynchronizationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BFDF671BDA6F9A0034A3DB /* HistorySynchronizationStatus.swift */; }; 54BFDF6A1BDA87D20034A3DB /* HistorySynchronizationStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BFDF691BDA87D20034A3DB /* HistorySynchronizationStatusTests.swift */; }; - 54C11BAD19D1EB7500576A96 /* ZMLoginTranscoderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54C11BAB19D1EB7500576A96 /* ZMLoginTranscoderTests.m */; }; 54C8A39C1F7536DB004961DF /* ZMOperationLoop+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54C8A39B1F7536DB004961DF /* ZMOperationLoop+Notifications.swift */; }; 54D785011A37256C00F47798 /* ZMEncodedNSUUIDWithTimestampTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D784FD1A37248000F47798 /* ZMEncodedNSUUIDWithTimestampTests.m */; }; 54DE26B31BC56E62002B5FBC /* ZMHotFixDirectory.h in Headers */ = {isa = PBXBuildFile; fileRef = 54DE26B11BC56E62002B5FBC /* ZMHotFixDirectory.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -280,6 +274,7 @@ 591B6E132C8B092B009F8A7B /* WireDataModelSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59D1C30F2B1DEE6E0016F6B2 /* WireDataModelSupport.framework */; }; 59271BE82B908DAC0019B726 /* SecurityClassificationProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59271BE72B908DAC0019B726 /* SecurityClassificationProviding.swift */; }; 59271BEA2B908E150019B726 /* SecurityClassification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59271BE92B908E150019B726 /* SecurityClassification.swift */; }; + 59537D892CFF9E7700920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59537D882CFF9E7700920B59 /* WireLogging */; }; 597B70C32B03984C006C2121 /* ZMUserSession+DeveloperMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597B70C22B03984C006C2121 /* ZMUserSession+DeveloperMenu.swift */; }; 598D04362C89C6FB00B64D71 /* WireFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 598D04352C89C6FB00B64D71 /* WireFoundation */; }; 598D04392C89C70500B64D71 /* WireFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 598D04382C89C70500B64D71 /* WireFoundation */; }; @@ -351,6 +346,8 @@ 70355A7327AAE62E00F02C76 /* ZMUserSession+SecurityClassificationProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70355A7227AAE62D00F02C76 /* ZMUserSession+SecurityClassificationProviding.swift */; }; 707CEBB727B515B200E080A4 /* ZMUserSessionTests+SecurityClassification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 707CEBB627B515B200E080A4 /* ZMUserSessionTests+SecurityClassification.swift */; }; 71AE6F20A2708DCF3BAD54F7 /* ZMOperationLoopTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3BF3961360B7EB12679AF27 /* ZMOperationLoopTests.swift */; }; + 7635D6932D031F0600E13F57 /* MockAPIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7635D6922D031F0600E13F57 /* MockAPIService.swift */; }; + 768BC8992D071B7D00846CFF /* ZMUserSession+IndividualToTeamMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 768BC8982D071B7300846CFF /* ZMUserSession+IndividualToTeamMigration.swift */; }; 7C26879D21F7193800570AA9 /* EventProcessingTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C26879C21F7193800570AA9 /* EventProcessingTracker.swift */; }; 7C419ED821F8D81D00B95770 /* EventProcessingTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C419ED621F8D7EB00B95770 /* EventProcessingTrackerTests.swift */; }; 7C5482DA225380160055F1AB /* CallReceivedResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C5482D9225380160055F1AB /* CallReceivedResult.swift */; }; @@ -413,7 +410,6 @@ BF44A3511C71D5FC00C6928E /* store127.wiredatabase in Resources */ = {isa = PBXBuildFile; fileRef = BF44A3501C71D5FC00C6928E /* store127.wiredatabase */; }; BF491CD11F03D7CF0055EE44 /* PermissionsDownloadRequestStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF491CD01F03D7CF0055EE44 /* PermissionsDownloadRequestStrategy.swift */; }; BF491CD51F03E0FC0055EE44 /* PermissionsDownloadRequestStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF491CD41F03E0FC0055EE44 /* PermissionsDownloadRequestStrategyTests.swift */; }; - BF50CFA71F39ACE8007833A4 /* MockUserInfoParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF50CFA51F39ABCF007833A4 /* MockUserInfoParser.swift */; }; BF6D5D031C4948830049F712 /* WireSyncEngine124.momd in Resources */ = {isa = PBXBuildFile; fileRef = BF6D5D021C4948830049F712 /* WireSyncEngine124.momd */; }; BF6D5D051C494D730049F712 /* WireSyncEngine125.momd in Resources */ = {isa = PBXBuildFile; fileRef = BF6D5D041C494D730049F712 /* WireSyncEngine125.momd */; }; BF735CFA1E70003D003BC61F /* SystemMessageCallObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF735CF91E70003D003BC61F /* SystemMessageCallObserver.swift */; }; @@ -443,9 +439,7 @@ E6BD767F2BDBAAE300FB1F8B /* CoreDataStack+Caches.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6BD767E2BDBAAE300FB1F8B /* CoreDataStack+Caches.swift */; }; E6C6C6B62B877429007585DF /* TeamMembersDownloadRequestStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6C6C6B42B87741D007585DF /* TeamMembersDownloadRequestStrategyTests.swift */; }; E6C6C6B72B87745F007585DF /* TeamMembersDownloadRequestStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6C6C6B22B877404007585DF /* TeamMembersDownloadRequestStrategy.swift */; }; - E6C983EC2B98627200D55177 /* GetMLSFeatureUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6C983EB2B98627200D55177 /* GetMLSFeatureUseCase.swift */; }; E6C983EE2B986ABA00D55177 /* ZMUserSession+UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6C983ED2B986ABA00D55177 /* ZMUserSession+UserSession.swift */; }; - E6D1B1F22B988166001CA68B /* GetMLSFeatureUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6D1B1F12B988166001CA68B /* GetMLSFeatureUseCaseTests.swift */; }; E6E5579A2BBD4D9C0033E62B /* ZMReachability+ServerConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6E557992BBD4D9C0033E62B /* ZMReachability+ServerConnection.swift */; }; E6F31B502C19B42E005DFA0C /* ZMUserSession+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6F31B4F2C19B42E005DFA0C /* ZMUserSession+Notifications.swift */; }; E910CF142CABF65E00B30E87 /* ToggleMessageReactionUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E910CF132CABF65E00B30E87 /* ToggleMessageReactionUseCaseTests.swift */; }; @@ -487,7 +481,6 @@ EE01E0391F90FEC1001AA33C /* ZMLocalNotificationTests_ExpiredMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE01E0381F90FEC1001AA33C /* ZMLocalNotificationTests_ExpiredMessage.swift */; }; EE0CAEAF2AAF2E8E00BD2DB7 /* URLSession+MinTLSVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0CAEAE2AAF2E8E00BD2DB7 /* URLSession+MinTLSVersion.swift */; }; EE0CAEB12AAF306000BD2DB7 /* ZMBlacklistDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = F9FD798519EE742600D70FCD /* ZMBlacklistDownloader.h */; }; - EE1108B723D1B367005DC663 /* TypingUsers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1108B623D1B367005DC663 /* TypingUsers.swift */; }; EE1108F923D1F945005DC663 /* TypingUsersTimeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1108F823D1F945005DC663 /* TypingUsersTimeout.swift */; }; EE1108FB23D2087F005DC663 /* Typing.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1108FA23D2087F005DC663 /* Typing.swift */; }; EE13BD8B2BC948FC006561F8 /* ZMUserSession+APIAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE13BD8A2BC948FC006561F8 /* ZMUserSession+APIAdapter.swift */; }; @@ -497,7 +490,6 @@ EE1DEBB923D5B9BC0087EE1F /* ZMConversation+TypingUsersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1DEBB823D5B9BC0087EE1F /* ZMConversation+TypingUsersTests.swift */; }; EE1DEBBE23D5E12F0087EE1F /* TypingUsersTimeout+Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1DEBBC23D5E0390087EE1F /* TypingUsersTimeout+Key.swift */; }; EE1DEBC423D5F1970087EE1F /* Conversation+TypingUsers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1DEBC223D5F1920087EE1F /* Conversation+TypingUsers.swift */; }; - EE1DEBC723D5F1F30087EE1F /* NSManagedObjectContext+TypingUsers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1DEBC523D5F1D00087EE1F /* NSManagedObjectContext+TypingUsers.swift */; }; EE2DE5E229250C1A00F42F4C /* CallHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE2DE5E129250C1A00F42F4C /* CallHandle.swift */; }; EE2DE5E429250CC400F42F4C /* CallKitCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE2DE5E329250CC400F42F4C /* CallKitCall.swift */; }; EE2DE5E8292519EC00F42F4C /* CallKitCallRegister.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE2DE5E7292519EC00F42F4C /* CallKitCallRegister.swift */; }; @@ -518,7 +510,6 @@ EE5D9B60290A7CEE007D78D6 /* VoIPPushManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5D9B5F290A7CEE007D78D6 /* VoIPPushManager.swift */; }; EE5D9B62290A8557007D78D6 /* SessionManager+VoIPPushManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5D9B61290A8557007D78D6 /* SessionManager+VoIPPushManagerDelegate.swift */; }; EE5FEF0521E8948F00E24F7F /* ZMUserSession+DarwinNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5FEF0421E8948F00E24F7F /* ZMUserSession+DarwinNotificationCenter.swift */; }; - EE6654642445D4EE00CBF8D3 /* MockAddressBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE6654632445D4EE00CBF8D3 /* MockAddressBook.swift */; }; EE668BB62954AA7F00D939E7 /* WireRequestStrategy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE668BB52954AA7F00D939E7 /* WireRequestStrategy.framework */; }; EE67F6C8296F0622001D7C88 /* libPhoneNumberiOS.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE67F6C5296F0622001D7C88 /* libPhoneNumberiOS.xcframework */; }; EE67F6CA296F0622001D7C88 /* ZipArchive.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE67F6C6296F0622001D7C88 /* ZipArchive.xcframework */; }; @@ -590,7 +581,6 @@ F905C47F1E79A86A00AF34A5 /* WireCallCenterV3Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F905C47E1E79A86A00AF34A5 /* WireCallCenterV3Tests.swift */; }; F90EC5A31E7BF1AC00A6779E /* AVSWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90EC5A21E7BF1AC00A6779E /* AVSWrapper.swift */; }; F9245BED1CBF95A8009D1E85 /* ZMHotFixDirectory+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9245BEC1CBF95A8009D1E85 /* ZMHotFixDirectory+Swift.swift */; }; - F925468E1C63B61000CE2D7C /* MessagingTest+EventFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F925468D1C63B61000CE2D7C /* MessagingTest+EventFactory.m */; }; F92CA9651F153622007D8570 /* CacheFileRelocatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92CA9641F153622007D8570 /* CacheFileRelocatorTests.swift */; }; F93A75F21C1F219800252586 /* ConversationStatusStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93A75F11C1F219800252586 /* ConversationStatusStrategy.swift */; }; F9410F631DE44C2E007451FF /* TypingStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9410F621DE44C2E007451FF /* TypingStrategyTests.swift */; }; @@ -614,7 +604,6 @@ F9C598AD1A0947B300B1F760 /* ZMBlacklistDownloaderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F9FD798919EE962F00D70FCD /* ZMBlacklistDownloaderTest.m */; }; F9C9A4F01CAD29190039E10C /* store128.wiredatabase in Resources */ = {isa = PBXBuildFile; fileRef = F9C9A4ED1CAD290B0039E10C /* store128.wiredatabase */; }; F9D1CD141DF6C131002F6E80 /* SyncStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D1CD131DF6C131002F6E80 /* SyncStatusTests.swift */; }; - F9DAC54F1C2035660001F11E /* ConversationStatusStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9DAC54D1C2034E70001F11E /* ConversationStatusStrategyTests.swift */; }; F9E577211E77EC6D0065EFE4 /* WireCallCenterV3+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E577201E77EC6D0065EFE4 /* WireCallCenterV3+Notifications.swift */; }; F9F631421DE3524100416938 /* TypingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9F631411DE3524100416938 /* TypingStrategy.swift */; }; F9F846351ED307F70087C1A4 /* CallParticipantsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9F846331ED307F10087C1A4 /* CallParticipantsSnapshotTests.swift */; }; @@ -674,6 +663,8 @@ 01D33D8029B8ED97009E94F3 /* SyncStatusLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStatusLog.swift; sourceTree = ""; }; 060C06622B73DB8800B484C6 /* SnoozeCertificateEnrollmentUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnoozeCertificateEnrollmentUseCase.swift; sourceTree = ""; }; 060C06672B7619E300B484C6 /* SelfClientCertificateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfClientCertificateProvider.swift; sourceTree = ""; }; + 06185A9F2CEBA632005D3CFE /* GetMLSFeatureUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetMLSFeatureUseCase.swift; sourceTree = ""; }; + 06185AA12CEBA6F5005D3CFE /* GetMLSFeatureUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetMLSFeatureUseCaseTests.swift; sourceTree = ""; }; 061F791A2B767AE100E8827B /* SelfClientCertificateProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfClientCertificateProviderTests.swift; sourceTree = ""; }; 061F791D2B76828500E8827B /* SnoozeCertificateEnrollmentUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnoozeCertificateEnrollmentUseCaseTests.swift; sourceTree = ""; }; 06239125274DB73A0065A72D /* StartLoginURLActionProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartLoginURLActionProcessor.swift; sourceTree = ""; }; @@ -703,6 +694,8 @@ 06E0979B2A261E5D00B38C4A /* ZMUserSession+RecurringAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSession+RecurringAction.swift"; sourceTree = ""; }; 06E0979F2A264F0300B38C4A /* ZMUserSessionTests+RecurringActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSessionTests+RecurringActions.swift"; sourceTree = ""; }; 06F98D5E24379143007E914A /* SignatureRequestStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignatureRequestStrategyTests.swift; sourceTree = ""; }; + 06FBF22E2CFD84FC00DA13EC /* UserSession+Federation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserSession+Federation.swift"; sourceTree = ""; }; + 06FC48072CF0E36A00D31461 /* SearchUsersUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchUsersUseCase.swift; sourceTree = ""; }; 0920833C1BA84F3100F82B29 /* UserClientRequestStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserClientRequestStrategyTests.swift; sourceTree = ""; }; 0920833F1BA95EE100F82B29 /* UserClientRequestFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserClientRequestFactory.swift; sourceTree = ""; }; 0920C4D81B305FF500C55728 /* UserSessionGiphyRequestStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserSessionGiphyRequestStateTests.swift; sourceTree = ""; }; @@ -710,7 +703,6 @@ 093694441BA9633300F36B3A /* UserClientRequestFactoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserClientRequestFactoryTests.swift; sourceTree = ""; }; 09531F131AE960E300B8556A /* ZMLoginCodeRequestTranscoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZMLoginCodeRequestTranscoder.h; sourceTree = ""; }; 09531F141AE960E300B8556A /* ZMLoginCodeRequestTranscoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMLoginCodeRequestTranscoder.m; sourceTree = ""; }; - 09531F1A1AE9644800B8556A /* ZMLoginCodeRequestTranscoderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMLoginCodeRequestTranscoderTests.m; sourceTree = ""; }; 09914E521BD6613D00C10BF8 /* ZMDecodedAPSMessageTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMDecodedAPSMessageTest.m; sourceTree = ""; }; 0994E1DD1B835C4900A51721 /* ios-test-host.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ios-test-host.xcconfig"; sourceTree = ""; }; 0994E1DE1B835C4900A51721 /* ios-test-target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "ios-test-target.xcconfig"; sourceTree = ""; }; @@ -802,7 +794,6 @@ 168474252252579A004DE9EC /* ZMUserSessionTests+Syncing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSessionTests+Syncing.swift"; sourceTree = ""; }; 16849B1B23EDA1FD00C025A8 /* MockUpdateEventProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUpdateEventProcessor.swift; sourceTree = ""; }; 16849B1F23EDB32B00C025A8 /* MockSessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSessionManager.swift; sourceTree = ""; }; - 168818A025F1512400BB51C3 /* AllTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = AllTests.xctestplan; sourceTree = ""; }; 168CF42620077C54009FCB89 /* TeamInvitationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamInvitationStatus.swift; sourceTree = ""; }; 168CF4282007840A009FCB89 /* Team+Invite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Team+Invite.swift"; sourceTree = ""; }; 168CF42A20079A02009FCB89 /* TeamInvitationRequestStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamInvitationRequestStrategy.swift; sourceTree = ""; }; @@ -839,14 +830,12 @@ 16ED865C23E30F7E00CB1766 /* ZMUserSesson+Proxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSesson+Proxy.swift"; sourceTree = ""; }; 16ED865E23E3145C00CB1766 /* ZMUserSession+Clients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSession+Clients.swift"; sourceTree = ""; }; 16F5F16B1E4092C00062F0AE /* NSManagedObjectContext+CTCallCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+CTCallCenter.swift"; sourceTree = ""; }; - 16F6BB371EDEA659009EA803 /* SearchResult+AddressBook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SearchResult+AddressBook.swift"; sourceTree = ""; }; 16FF47481F0CD6610044C491 /* IntegrationTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IntegrationTest.h; sourceTree = ""; }; 16FF474B1F0D54B20044C491 /* SessionManager+Logs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SessionManager+Logs.swift"; sourceTree = ""; }; 259AAB0C2B9F477F00B13A7C /* LastE2EIdentityUpdateDateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastE2EIdentityUpdateDateProtocol.swift; sourceTree = ""; }; 25CCE9DB2BA4A943002AB21F /* String+Mocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Mocks.swift"; sourceTree = ""; }; 25E11B0A2B56A15F005D51FA /* GetE2eIdentityCertificatesUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetE2eIdentityCertificatesUseCase.swift; sourceTree = ""; }; 25E11B0C2B56B497005D51FA /* GetIsE2EIdentityEnabledUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetIsE2EIdentityEnabledUseCase.swift; sourceTree = ""; }; - 2B56189B29A5074000D8CCCA /* SecurityTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SecurityTests.xctestplan; sourceTree = ""; }; 3D6B0837E10BD4D5E88805E3 /* ZMSyncStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZMSyncStrategyTests.swift; sourceTree = ""; }; 3E05F247192A4F8900F22D80 /* NSError+ZMUserSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+ZMUserSession.h"; sourceTree = ""; }; 3E05F250192A4FBD00F22D80 /* UserSessionErrorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserSessionErrorTests.m; sourceTree = ""; }; @@ -855,7 +844,6 @@ 3E1858B21951D69B005FE78F /* MemoryLeaksObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MemoryLeaksObserver.h; sourceTree = ""; }; 3E1858B31951D69B005FE78F /* MemoryLeaksObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MemoryLeaksObserver.m; sourceTree = ""; }; 3E18605C191A4F3B000FE027 /* WireSyncEngine-iOS.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireSyncEngine-iOS.pch"; sourceTree = ""; }; - 3E18605F191A4F6A000FE027 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = ""; }; 3E186088191A56F6000FE027 /* WireSyncEngine Test Host.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "WireSyncEngine Test Host.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 3E1860B4191A5D99000FE027 /* Test-Host-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Test-Host-Info.plist"; sourceTree = ""; }; 3E1860B5191A5D99000FE027 /* Test-Host-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Test-Host-Prefix.pch"; sourceTree = ""; }; @@ -873,7 +861,6 @@ 541228431AEE422C00D9ED1C /* ZMAuthenticationStatusTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMAuthenticationStatusTests.m; sourceTree = ""; }; 54131BC525C7F71400CE2CA2 /* LoginDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginDelegate.swift; sourceTree = ""; }; 54131BCD25C7FFCA00CE2CA2 /* SessionManager+AuthenticationStatusDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionManager+AuthenticationStatusDelegate.swift"; sourceTree = ""; }; - 54188DCA19D19DE200DA40E4 /* ZMLastUpdateEventIDTranscoderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMLastUpdateEventIDTranscoderTests.m; sourceTree = ""; }; 542049EF196AB84B000D8A94 /* WireSyncEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WireSyncEngine.h; sourceTree = ""; }; 5423B999191A4A1B0044347D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 54257C071DF1C94200107FE7 /* TopConversationsDirectory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopConversationsDirectory.swift; sourceTree = ""; }; @@ -882,7 +869,6 @@ 5427B34E19D195A100CC18DC /* ZMLastUpdateEventIDTranscoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ZMLastUpdateEventIDTranscoder.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 5427B35319D1965A00CC18DC /* ZMLastUpdateEventIDTranscoder+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ZMLastUpdateEventIDTranscoder+Internal.h"; sourceTree = ""; }; 542DFEE51DDCA452000F5B95 /* UserProfileUpdateStatusTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserProfileUpdateStatusTests.swift; sourceTree = ""; }; - 542DFEE71DDCA4FD000F5B95 /* UserProfileUpdateRequestStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserProfileUpdateRequestStrategyTests.swift; sourceTree = ""; }; 543095921DE76B170065367F /* random1.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = random1.txt; sourceTree = ""; }; 543095941DE76B270065367F /* random2.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = random2.txt; sourceTree = ""; }; 5430E9231BAA0D9F00395E05 /* WireSyncEngineLogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WireSyncEngineLogs.h; sourceTree = ""; }; @@ -891,7 +877,6 @@ 545643D41C62C1A800A2129C /* ConversationTestsBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConversationTestsBase.h; sourceTree = ""; }; 5458273D2541C3A9002B8F83 /* PresentationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationDelegate.swift; sourceTree = ""; }; 5458AF831F7021B800E45977 /* PreLoginAuthenticationNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreLoginAuthenticationNotification.swift; sourceTree = ""; }; - 545F601B1D6C336D00C2C55B /* AddressBookSearchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressBookSearchTests.swift; sourceTree = ""; }; 546392711D79D5210094EC66 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; 5463C88F193F38A6006799DE /* ZMTimingTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZMTimingTests.h; sourceTree = ""; }; 5463C890193F38A6006799DE /* ZMTimingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMTimingTests.m; sourceTree = ""; }; @@ -909,18 +894,14 @@ 5478A1401DEC4048006F7268 /* UserProfile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = ""; }; 547E5B571DDB4B800038D936 /* UserProfileUpdateStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserProfileUpdateStatus.swift; sourceTree = ""; }; 547E5B591DDB67390038D936 /* UserProfileUpdateRequestStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserProfileUpdateRequestStrategy.swift; sourceTree = ""; }; - 549552511D64567C004F21F6 /* AddressBookTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressBookTests.swift; sourceTree = ""; }; 549710071F6FF5C100026EDD /* NotificationInContext+UserSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NotificationInContext+UserSession.swift"; sourceTree = ""; }; 549710091F6FFE9900026EDD /* ClientUpdateNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientUpdateNotification.swift; sourceTree = ""; }; 54973A351DD48CAB007F8702 /* NSManagedObject+CryptoStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+CryptoStack.swift"; sourceTree = ""; }; 549815931A43232400A7CE2E /* WireSyncEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WireSyncEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 549815961A43232400A7CE2E /* WireSyncEngine-ios-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "WireSyncEngine-ios-Info.plist"; sourceTree = ""; }; - 54991D571DEDCF2B007E282F /* AddressBook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressBook.swift; sourceTree = ""; }; - 54991D591DEDD07E007E282F /* ContactAddressBook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactAddressBook.swift; sourceTree = ""; }; 54A170631B300696001B41A5 /* ProxiedRequestStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxiedRequestStrategy.swift; sourceTree = ""; }; 54A170671B300717001B41A5 /* ProxiedRequestStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProxiedRequestStrategyTests.swift; sourceTree = ""; }; 54A227D51D6604A5009414C0 /* SynchronizationMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SynchronizationMocks.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - 54A343461D6B589A004B65EA /* AddressBookSearch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressBookSearch.swift; sourceTree = ""; }; 54AB428D1DF5C5B400381F2C /* TopConversationsDirectoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopConversationsDirectoryTests.swift; sourceTree = ""; }; 54BD32D01A5ACCF9008EB1B0 /* Test-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Test-Bridging-Header.h"; sourceTree = ""; }; 54BFDF671BDA6F9A0034A3DB /* HistorySynchronizationStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistorySynchronizationStatus.swift; sourceTree = ""; }; @@ -928,7 +909,6 @@ 54C11B9E19D1E4A100576A96 /* ZMLoginTranscoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZMLoginTranscoder.h; sourceTree = ""; }; 54C11B9F19D1E4A100576A96 /* ZMLoginTranscoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMLoginTranscoder.m; sourceTree = ""; }; 54C11BA819D1E70900576A96 /* ZMLoginTranscoder+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ZMLoginTranscoder+Internal.h"; sourceTree = ""; }; - 54C11BAB19D1EB7500576A96 /* ZMLoginTranscoderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMLoginTranscoderTests.m; sourceTree = ""; }; 54C8A39B1F7536DB004961DF /* ZMOperationLoop+Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ZMOperationLoop+Notifications.swift"; sourceTree = ""; }; 54D784FD1A37248000F47798 /* ZMEncodedNSUUIDWithTimestampTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMEncodedNSUUIDWithTimestampTests.m; sourceTree = ""; }; 54DE26B11BC56E62002B5FBC /* ZMHotFixDirectory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZMHotFixDirectory.h; sourceTree = ""; }; @@ -942,9 +922,6 @@ 54F8D6E519AB535700146664 /* ZMMissingUpdateEventsTranscoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ZMMissingUpdateEventsTranscoder.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 54F8D6E819AB535700146664 /* ZMSelfStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZMSelfStrategy.h; sourceTree = ""; }; 54F8D6E919AB535700146664 /* ZMSelfStrategy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMSelfStrategy.m; sourceTree = ""; }; - 54F8D73119AB677400146664 /* ZMSelfTranscoderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMSelfTranscoderTests.m; sourceTree = ""; }; - 54F8D74819AB67B300146664 /* ObjectTranscoderTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectTranscoderTests.h; sourceTree = ""; }; - 54F8D74919AB67B300146664 /* ObjectTranscoderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjectTranscoderTests.m; sourceTree = ""; }; 54FEAAA81BC7BB9C002DE521 /* ZMBlacklistDownloader+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ZMBlacklistDownloader+Testing.h"; sourceTree = ""; }; 54FF64281F73D00C00787EF2 /* NSManagedObjectContext+AuthenticationStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+AuthenticationStatus.swift"; sourceTree = ""; }; 554FEE2022AFF20600B1A8A1 /* ZMUserSession+LegalHold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSession+LegalHold.swift"; sourceTree = ""; }; @@ -1017,6 +994,8 @@ 63FE4B9D25C1D2EC002878E5 /* VideoGridPresentationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoGridPresentationMode.swift; sourceTree = ""; }; 70355A7227AAE62D00F02C76 /* ZMUserSession+SecurityClassificationProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSession+SecurityClassificationProviding.swift"; sourceTree = ""; }; 707CEBB627B515B200E080A4 /* ZMUserSessionTests+SecurityClassification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSessionTests+SecurityClassification.swift"; sourceTree = ""; }; + 7635D6922D031F0600E13F57 /* MockAPIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAPIService.swift; sourceTree = ""; }; + 768BC8982D071B7300846CFF /* ZMUserSession+IndividualToTeamMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSession+IndividualToTeamMigration.swift"; sourceTree = ""; }; 7C26879C21F7193800570AA9 /* EventProcessingTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProcessingTracker.swift; sourceTree = ""; }; 7C419ED621F8D7EB00B95770 /* EventProcessingTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProcessingTrackerTests.swift; sourceTree = ""; }; 7C5482D9225380160055F1AB /* CallReceivedResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallReceivedResult.swift; sourceTree = ""; }; @@ -1124,7 +1103,6 @@ BF44A3501C71D5FC00C6928E /* store127.wiredatabase */ = {isa = PBXFileReference; lastKnownFileType = file; path = store127.wiredatabase; sourceTree = ""; }; BF491CD01F03D7CF0055EE44 /* PermissionsDownloadRequestStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionsDownloadRequestStrategy.swift; sourceTree = ""; }; BF491CD41F03E0FC0055EE44 /* PermissionsDownloadRequestStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionsDownloadRequestStrategyTests.swift; sourceTree = ""; }; - BF50CFA51F39ABCF007833A4 /* MockUserInfoParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockUserInfoParser.swift; sourceTree = ""; }; BF6D5D021C4948830049F712 /* WireSyncEngine124.momd */ = {isa = PBXFileReference; lastKnownFileType = folder; path = WireSyncEngine124.momd; sourceTree = ""; }; BF6D5D041C494D730049F712 /* WireSyncEngine125.momd */ = {isa = PBXFileReference; lastKnownFileType = folder; path = WireSyncEngine125.momd; sourceTree = ""; }; BF735CF91E70003D003BC61F /* SystemMessageCallObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemMessageCallObserver.swift; sourceTree = ""; }; @@ -1157,9 +1135,7 @@ E6BD767E2BDBAAE300FB1F8B /* CoreDataStack+Caches.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreDataStack+Caches.swift"; sourceTree = ""; }; E6C6C6B22B877404007585DF /* TeamMembersDownloadRequestStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamMembersDownloadRequestStrategy.swift; sourceTree = ""; }; E6C6C6B42B87741D007585DF /* TeamMembersDownloadRequestStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamMembersDownloadRequestStrategyTests.swift; sourceTree = ""; }; - E6C983EB2B98627200D55177 /* GetMLSFeatureUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetMLSFeatureUseCase.swift; sourceTree = ""; }; E6C983ED2B986ABA00D55177 /* ZMUserSession+UserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSession+UserSession.swift"; sourceTree = ""; }; - E6D1B1F12B988166001CA68B /* GetMLSFeatureUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetMLSFeatureUseCaseTests.swift; sourceTree = ""; }; E6E557992BBD4D9C0033E62B /* ZMReachability+ServerConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMReachability+ServerConnection.swift"; sourceTree = ""; }; E6F31B4F2C19B42E005DFA0C /* ZMUserSession+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSession+Notifications.swift"; sourceTree = ""; }; E910CF132CABF65E00B30E87 /* ToggleMessageReactionUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleMessageReactionUseCaseTests.swift; sourceTree = ""; }; @@ -1200,7 +1176,6 @@ EE01E0381F90FEC1001AA33C /* ZMLocalNotificationTests_ExpiredMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZMLocalNotificationTests_ExpiredMessage.swift; sourceTree = ""; }; EE0BA28F29D5DBD6004E93B5 /* MockCryptoboxMigrationManagerInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCryptoboxMigrationManagerInterface.swift; sourceTree = ""; }; EE0CAEAE2AAF2E8E00BD2DB7 /* URLSession+MinTLSVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+MinTLSVersion.swift"; sourceTree = ""; }; - EE1108B623D1B367005DC663 /* TypingUsers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingUsers.swift; sourceTree = ""; }; EE1108F823D1F945005DC663 /* TypingUsersTimeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingUsersTimeout.swift; sourceTree = ""; }; EE1108FA23D2087F005DC663 /* Typing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Typing.swift; sourceTree = ""; }; EE13BD8A2BC948FC006561F8 /* ZMUserSession+APIAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSession+APIAdapter.swift"; sourceTree = ""; }; @@ -1210,7 +1185,6 @@ EE1DEBB823D5B9BC0087EE1F /* ZMConversation+TypingUsersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMConversation+TypingUsersTests.swift"; sourceTree = ""; }; EE1DEBBC23D5E0390087EE1F /* TypingUsersTimeout+Key.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TypingUsersTimeout+Key.swift"; sourceTree = ""; }; EE1DEBC223D5F1920087EE1F /* Conversation+TypingUsers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Conversation+TypingUsers.swift"; sourceTree = ""; }; - EE1DEBC523D5F1D00087EE1F /* NSManagedObjectContext+TypingUsers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+TypingUsers.swift"; sourceTree = ""; }; EE2DE5E129250C1A00F42F4C /* CallHandle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallHandle.swift; sourceTree = ""; }; EE2DE5E329250CC400F42F4C /* CallKitCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallKitCall.swift; sourceTree = ""; }; EE2DE5E7292519EC00F42F4C /* CallKitCallRegister.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallKitCallRegister.swift; sourceTree = ""; }; @@ -1231,7 +1205,6 @@ EE5D9B5F290A7CEE007D78D6 /* VoIPPushManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoIPPushManager.swift; sourceTree = ""; }; EE5D9B61290A8557007D78D6 /* SessionManager+VoIPPushManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionManager+VoIPPushManagerDelegate.swift"; sourceTree = ""; }; EE5FEF0421E8948F00E24F7F /* ZMUserSession+DarwinNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSession+DarwinNotificationCenter.swift"; sourceTree = ""; }; - EE6654632445D4EE00CBF8D3 /* MockAddressBook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockAddressBook.swift; sourceTree = ""; }; EE668BB52954AA7F00D939E7 /* WireRequestStrategy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireRequestStrategy.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE668BB92954AA9300D939E7 /* WireMockTransport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireMockTransport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE67F6C5296F0622001D7C88 /* libPhoneNumberiOS.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libPhoneNumberiOS.xcframework; path = ../Carthage/Build/libPhoneNumberiOS.xcframework; sourceTree = ""; }; @@ -1332,8 +1305,6 @@ F91CA6AE1BECBD51000EE5C2 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Push.strings; sourceTree = ""; }; F91CA6AF1BECBD51000EE5C2 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = de; path = de.lproj/Push.stringsdict; sourceTree = ""; }; F9245BEC1CBF95A8009D1E85 /* ZMHotFixDirectory+Swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ZMHotFixDirectory+Swift.swift"; sourceTree = ""; }; - F925468C1C63B61000CE2D7C /* MessagingTest+EventFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MessagingTest+EventFactory.h"; sourceTree = ""; }; - F925468D1C63B61000CE2D7C /* MessagingTest+EventFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MessagingTest+EventFactory.m"; sourceTree = ""; }; F92CA9641F153622007D8570 /* CacheFileRelocatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheFileRelocatorTests.swift; sourceTree = ""; }; F93A75F11C1F219800252586 /* ConversationStatusStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationStatusStrategy.swift; sourceTree = ""; }; F9410F621DE44C2E007451FF /* TypingStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingStrategyTests.swift; sourceTree = ""; }; @@ -1363,7 +1334,6 @@ F9B71F4B1CB2B841001DB03F /* NSManagedObjectContext+TestHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObjectContext+TestHelpers.m"; sourceTree = ""; }; F9C9A4ED1CAD290B0039E10C /* store128.wiredatabase */ = {isa = PBXFileReference; lastKnownFileType = file; path = store128.wiredatabase; sourceTree = ""; }; F9D1CD131DF6C131002F6E80 /* SyncStatusTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SyncStatusTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - F9DAC54D1C2034E70001F11E /* ConversationStatusStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationStatusStrategyTests.swift; sourceTree = ""; }; F9E3AB511BEA017300C1A6AA /* ZMSelfStrategy+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ZMSelfStrategy+Internal.h"; sourceTree = ""; }; F9E577201E77EC6D0065EFE4 /* WireCallCenterV3+Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WireCallCenterV3+Notifications.swift"; sourceTree = ""; }; F9F11A061A0A630900F1DCEE /* ZMBlacklistVerificatorTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMBlacklistVerificatorTest.m; sourceTree = ""; }; @@ -1377,6 +1347,11 @@ F9FD798C19EE9B9A00D70FCD /* ZMBlacklistVerificator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMBlacklistVerificator.m; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 59C60D1A2D00672300CAC544 /* Transcoders */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Transcoders; sourceTree = ""; }; + 59D264272CF722430005317F /* TestsPlans */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = TestsPlans; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 0145AE7E2B1154010097E3B8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -1399,6 +1374,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 017F32C92D03AC3000471B3D /* WireAPI in Frameworks */, 0155194E2C20D29400037358 /* WireDomainSupport.framework in Frameworks */, 5996E8922C19CB28007A52F0 /* WireSystemSupport.framework in Frameworks */, 59FFADA02C8229BB000C8085 /* WireTransportSupport.framework in Frameworks */, @@ -1424,6 +1400,7 @@ 59B99FAE2C89DF7B00201827 /* WireDomainPackage in Frameworks */, 598D04362C89C6FB00B64D71 /* WireFoundation in Frameworks */, EE67F6C8296F0622001D7C88 /* libPhoneNumberiOS.xcframework in Frameworks */, + 59537D892CFF9E7700920B59 /* WireLogging in Frameworks */, 01F0F8922CCA2B2C00FE4170 /* avs.xcframework in Frameworks */, EE67F6CA296F0622001D7C88 /* ZipArchive.xcframework in Frameworks */, ); @@ -1449,11 +1426,11 @@ 16D74BF52B5A961800160298 /* ChangeUsernameUseCaseTests.swift */, EEA58F0F2B70D59F006DEE32 /* CreateTeamOneOnOneConversationUseCaseTests.swift */, EE8B934D2B838AFD00D5E670 /* GetE2eIdentityCertificatesUseCaseTests.swift */, - E6D1B1F12B988166001CA68B /* GetMLSFeatureUseCaseTests.swift */, 018A3DBE2B0799CA00EB3D6B /* GetUserClientFingerprintUseCaseTests.swift */, E98E5DFB2B8DF45100A2CFF5 /* ResolveOneOnOneConversationsUseCaseTests.swift */, 061F791D2B76828500E8827B /* SnoozeCertificateEnrollmentUseCaseTests.swift */, 06D16AAF2B9F3C9F00D5A28A /* E2EIdentityCertificateUpdateStatusUseCaseTests.swift */, + 06185AA12CEBA6F5005D3CFE /* GetMLSFeatureUseCaseTests.swift */, E9F7FE282BE0C61900699356 /* SetAllowGuestsAndServicesUseCaseTests.swift */, 017704DB2CA44C0D00BE69ED /* DisableAnalyticsUseCaseTests.swift */, 017704D92CA44C0D00BE69ED /* EnableAnalyticsUseCaseTests.swift */, @@ -1584,18 +1561,10 @@ path = URLActionProcessors; sourceTree = ""; }; - 1688189C25F150E100BB51C3 /* TestsPlans */ = { - isa = PBXGroup; - children = ( - 168818A025F1512400BB51C3 /* AllTests.xctestplan */, - 2B56189B29A5074000D8CCCA /* SecurityTests.xctestplan */, - ); - path = TestsPlans; - sourceTree = ""; - }; 169BA1F625ECF8C300374343 /* Mocks */ = { isa = PBXGroup; children = ( + 7635D6922D031F0600E13F57 /* MockAPIService.swift */, E9D6FC252CD4EC5C007924B8 /* MockLabelType.swift */, 169BA1F725ECF8F700374343 /* MockAppLock.swift */, 169BA23C25EF6E2A00374343 /* MockRegistrationStatusDelegate.swift */, @@ -1696,9 +1665,7 @@ EE1108FA23D2087F005DC663 /* Typing.swift */, EE1108F823D1F945005DC663 /* TypingUsersTimeout.swift */, EE1DEBBC23D5E0390087EE1F /* TypingUsersTimeout+Key.swift */, - EE1108B623D1B367005DC663 /* TypingUsers.swift */, 16F5F16B1E4092C00062F0AE /* NSManagedObjectContext+CTCallCenter.swift */, - EE1DEBC523D5F1D00087EE1F /* NSManagedObjectContext+TypingUsers.swift */, 0640C26C26EA0B5C0057AF80 /* NSManagedObjectContext+Packaging.swift */, 16085B321F71811A000B9F22 /* UserChangeInfo+UserSession.swift */, 8754B8491F73C25400EC02AD /* ConversationListChangeInfo+UserSession.swift */, @@ -1784,7 +1751,6 @@ children = ( EEDDB6A32BCFC5E4009ECF97 /* WireSyncEngine.docc */, 09284B6A1B8272C300EEE10E /* WireSyncEngine Test Host.entitlements */, - 3E18605F191A4F6A000FE027 /* README.md */, 5423B98C191A49CD0044347D /* Source */, 3E18605E191A4F4F000FE027 /* Resources */, 5423B997191A4A1B0044347D /* Tests */, @@ -1857,7 +1823,7 @@ 5423B997191A4A1B0044347D /* Tests */ = { isa = PBXGroup; children = ( - 1688189C25F150E100BB51C3 /* TestsPlans */, + 59D264272CF722430005317F /* TestsPlans */, 5474C7DC1921303400185A3A /* Source */, 3E1860B3191A5D1D000FE027 /* iOS Test Host */, 3E18605A191A4EF8000FE027 /* Resources */, @@ -1903,7 +1869,6 @@ 166B2B6D23EB2CD3003E8581 /* DatabaseTest.swift */, 87AEA67B1EFBD27700C94BF3 /* DiskDatabaseTest.swift */, 5E0EB1F82100A46F00B5DC2B /* MockCompanyLoginRequesterDelegate.swift */, - EE6654632445D4EE00CBF8D3 /* MockAddressBook.swift */, CBD3AECF2C4A742B007643A0 /* TestSetup.swift */, ); path = Source; @@ -1987,7 +1952,6 @@ 1660AA101ECE3C1C0056D403 /* SearchTaskTests.swift */, 164C29A21ECF437E0026562A /* SearchRequestTests.swift */, 164C29A41ECF47D80026562A /* SearchDirectoryTests.swift */, - 545F601B1D6C336D00C2C55B /* AddressBookSearchTests.swift */, 54AB428D1DF5C5B400381F2C /* TopConversationsDirectoryTests.swift */, ); name = Search; @@ -2127,7 +2091,6 @@ 54CEC9BB19AB34CE006817BB /* Registration */ = { isa = PBXGroup; children = ( - 549552511D64567C004F21F6 /* AddressBookTests.swift */, 54DE9BEC1DE75D4900EFFB9C /* RandomHandleGeneratorTests.swift */, 5E8EE1FB20FDCCE200DB1F9B /* CompanyLoginRequestDetectorTests.swift */, 5E0EB1F52100A13200B5DC2B /* CompanyLoginRequesterTests.swift */, @@ -2159,23 +2122,6 @@ path = Transcoders; sourceTree = ""; }; - 54F8D72919AB66CB00146664 /* Transcoders */ = { - isa = PBXGroup; - children = ( - A97042E019E2BEC700FE746B /* Helper */, - 54F8D74819AB67B300146664 /* ObjectTranscoderTests.h */, - 54F8D74919AB67B300146664 /* ObjectTranscoderTests.m */, - F9DAC54D1C2034E70001F11E /* ConversationStatusStrategyTests.swift */, - 54188DCA19D19DE200DA40E4 /* ZMLastUpdateEventIDTranscoderTests.m */, - 54F8D73119AB677400146664 /* ZMSelfTranscoderTests.m */, - 542DFEE71DDCA4FD000F5B95 /* UserProfileUpdateRequestStrategyTests.swift */, - 54C11BAB19D1EB7500576A96 /* ZMLoginTranscoderTests.m */, - BF50CFA51F39ABCF007833A4 /* MockUserInfoParser.swift */, - 09531F1A1AE9644800B8556A /* ZMLoginCodeRequestTranscoderTests.m */, - ); - path = Transcoders; - sourceTree = ""; - }; 54FC8A0E192CD52800D3C016 /* Integration */ = { isa = PBXGroup; children = ( @@ -2315,7 +2261,7 @@ 85D850FC5E45F9F688A64419 /* Synchronization */ = { isa = PBXGroup; children = ( - 54F8D72919AB66CB00146664 /* Transcoders */, + 59C60D1A2D00672300CAC544 /* Transcoders */, 54A170661B300700001B41A5 /* Strategies */, 85D85104C6D06FA902E3253C /* ZMSyncStrategyTests.m */, 85D858D72B109C5D9A85645B /* ZMOperationLoopTests.m */, @@ -2400,8 +2346,6 @@ F1C51FE61FB49660009C2269 /* RegistrationStatus.swift */, F148F6661FB9AA7600BD6909 /* UnregisteredTeam.swift */, 5EFE9C14212AB138007932A6 /* UnregisteredUser+Payload.swift */, - 54991D571DEDCF2B007E282F /* AddressBook.swift */, - 54991D591DEDD07E007E282F /* ContactAddressBook.swift */, 54DE9BEA1DE74FFB00EFFB9C /* RandomHandleGenerator.swift */, 5E8EE1F820FDC7C900DB1F9B /* Company */, ); @@ -2417,23 +2361,12 @@ path = Notifications; sourceTree = ""; }; - A97042E019E2BEC700FE746B /* Helper */ = { - isa = PBXGroup; - children = ( - F925468C1C63B61000CE2D7C /* MessagingTest+EventFactory.h */, - F925468D1C63B61000CE2D7C /* MessagingTest+EventFactory.m */, - ); - path = Helper; - sourceTree = ""; - }; A9BABE5E19BA1EF300E9E5A3 /* Search */ = { isa = PBXGroup; children = ( - 54A343461D6B589A004B65EA /* AddressBookSearch.swift */, 1660AA081ECCAC900056D403 /* SearchDirectory.swift */, 1660AA0C1ECDB0250056D403 /* SearchTask.swift */, 164C29A61ED2D7B00026562A /* SearchResult.swift */, - 16F6BB371EDEA659009EA803 /* SearchResult+AddressBook.swift */, 1660AA0A1ECCAF4E0056D403 /* SearchRequest.swift */, 54257C071DF1C94200107FE7 /* TopConversationsDirectory.swift */, ); @@ -2547,6 +2480,7 @@ A93B528A250101AC0061255E /* ZMUserSession+Debugging.swift */, 597B70C22B03984C006C2121 /* ZMUserSession+DeveloperMenu.swift */, 16E6F26124B371190015B249 /* ZMUserSession+EncryptionAtRest.swift */, + 768BC8982D071B7300846CFF /* ZMUserSession+IndividualToTeamMigration.swift */, E662328C2BF4BBED002B680A /* ZMUserSession+MLS.swift */, E6F31B4F2C19B42E005DFA0C /* ZMUserSession+Notifications.swift */, 554FEE2022AFF20600B1A8A1 /* ZMUserSession+LegalHold.swift */, @@ -2561,6 +2495,7 @@ E6425FDE2BCD5B9D003EC8CF /* ZMUserSessionBuilder.swift */, 16ED865C23E30F7E00CB1766 /* ZMUserSesson+Proxy.swift */, E9ACAED02CBE43C2000E13B5 /* ZMUserSession+WireCallStateObserver.swift */, + 06FBF22E2CFD84FC00DA13EC /* UserSession+Federation.swift */, ); path = ZMUserSession; sourceTree = ""; @@ -2589,8 +2524,8 @@ EE8584DA2B6938390045EAD4 /* CreateTeamOneOnOneConversationUseCase.swift */, EEE46E5628C5EF56005F48D7 /* FetchUserClientsUseCase.swift */, 25E11B0A2B56A15F005D51FA /* GetE2eIdentityCertificatesUseCase.swift */, - E6C983EB2B98627200D55177 /* GetMLSFeatureUseCase.swift */, 25E11B0C2B56B497005D51FA /* GetIsE2EIdentityEnabledUseCase.swift */, + 06185A9F2CEBA632005D3CFE /* GetMLSFeatureUseCase.swift */, 018A3DBB2B07998400EB3D6B /* GetUserClientFingerprintUseCase.swift */, E9A96E7D2B88E0EA00914FDD /* ResolveOneOnOneConversationsUseCase.swift */, 060C06622B73DB8800B484C6 /* SnoozeCertificateEnrollmentUseCase.swift */, @@ -2614,6 +2549,7 @@ E91180DE2CA6E86400DD63E2 /* ToggleMessageReactionUseCase.swift */, 0148133F2CC0522500F36DAA /* SubmitCallQualitySurveyUseCase.swift */, E9D6FC202CD4E496007924B8 /* UpdateConversationFolderUseCase.swift */, + 06FC48072CF0E36A00D31461 /* SearchUsersUseCase.swift */, ); path = "Use cases"; sourceTree = ""; @@ -2857,10 +2793,14 @@ A9FF8089195B17B3002CD44B /* PBXTargetDependency */, 0145AE932B1155760097E3B8 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 59C60D1A2D00672300CAC544 /* Transcoders */, + ); name = UnitTests; packageProductDependencies = ( 598D04382C89C70500B64D71 /* WireFoundation */, 01A532502CCA218A005FD421 /* WireAnalyticsSupport */, + 017F32C82D03AC3000471B3D /* WireAPI */, ); productName = "WireSyncEngine-iOS-Tests"; productReference = 3E1860C3191A649D000FE027 /* UnitTests.xctest */; @@ -2885,6 +2825,7 @@ 598D04352C89C6FB00B64D71 /* WireFoundation */, 59B99FAD2C89DF7B00201827 /* WireDomainPackage */, E9C60E912C259F3C004E5F13 /* WireAnalytics */, + 59537D882CFF9E7700920B59 /* WireLogging */, ); productName = "WireSyncEngine-ios"; productReference = 549815931A43232400A7CE2E /* WireSyncEngine.framework */; @@ -3080,7 +3021,6 @@ 5E2C354D21A806A80034F1EE /* MockBackgroundActivityManager.swift in Sources */, F9D1CD141DF6C131002F6E80 /* SyncStatusTests.swift in Sources */, EE1DEBB523D5AEE50087EE1F /* TypingUsersTimeoutTests.swift in Sources */, - 09531F1C1AE9644800B8556A /* ZMLoginCodeRequestTranscoderTests.m in Sources */, 87D003FF1BB5810D00472E06 /* APSSignalingKeyStoreTests.swift in Sources */, 542DFEE61DDCA452000F5B95 /* UserProfileUpdateStatusTests.swift in Sources */, F95FFBD11EB8A478004031CB /* CallSystemMessageGeneratorTests.swift in Sources */, @@ -3090,8 +3030,8 @@ 018A3DBF2B0799CA00EB3D6B /* GetUserClientFingerprintUseCaseTests.swift in Sources */, BF491CD51F03E0FC0055EE44 /* PermissionsDownloadRequestStrategyTests.swift in Sources */, 54BFDF6A1BDA87D20034A3DB /* HistorySynchronizationStatusTests.swift in Sources */, - 545434AC19AB6ADA003892D9 /* ZMSelfTranscoderTests.m in Sources */, 54A227D61D6604A5009414C0 /* SynchronizationMocks.swift in Sources */, + 7635D6932D031F0600E13F57 /* MockAPIService.swift in Sources */, 54A170691B300717001B41A5 /* ProxiedRequestStrategyTests.swift in Sources */, 06ADEA082BD2723F008BA0B3 /* RemoveUserClientUseCaseTests.swift in Sources */, 1660AA111ECE3C1C0056D403 /* SearchTaskTests.swift in Sources */, @@ -3105,7 +3045,6 @@ E98E5DFC2B8DF45100A2CFF5 /* ResolveOneOnOneConversationsUseCaseTests.swift in Sources */, 06ADE9EA2BC02B65008BA0B3 /* CertificateRevocationListsCheckerTests.swift in Sources */, F148F6691FBAF55800BD6909 /* TeamRegistrationStrategyTests.swift in Sources */, - 168C0B3F1E97CE3900315044 /* ZMLastUpdateEventIDTranscoderTests.m in Sources */, EE3E43F92BF62BF6001A43A1 /* SelfSupportedProtocolsRequestStrategyTests.swift in Sources */, E6C6C6B62B877429007585DF /* TeamMembersDownloadRequestStrategyTests.swift in Sources */, 879634421F7BEC5100FC79BA /* DispatchQueueSerialAsyncTests.swift in Sources */, @@ -3115,7 +3054,6 @@ 636826F82953465F00D904C2 /* ZMUserSessionTests+AccessToken.swift in Sources */, 0601900B2678750D0043F8F8 /* DeepLinkURLActionProcessorTests.swift in Sources */, 017704E42CA4503C00BE69ED /* EnableAnalyticsUseCaseTests.swift in Sources */, - 549552541D645683004F21F6 /* AddressBookTests.swift in Sources */, 161C886623FD248A00CB0B8E /* RecordingMockTransportSession.swift in Sources */, F19F4F4D1E646C3C00F4D8FF /* UserProfileImageUpdateStatusTests.swift in Sources */, 1679D1CD1EF97387007B0DF5 /* ZMUserSessionTestsBase+Calling.swift in Sources */, @@ -3123,14 +3061,12 @@ 167F383D23E04A93006B6AA9 /* UnauthenticatedSessionTests+SSO.swift in Sources */, 63C4B3C82C36A4C900C09A93 /* FetchShareableConversationUseCaseTests.swift in Sources */, 16519D6D231EAAF300C9D76D /* Conversation+DeletionTests.swift in Sources */, - F925468E1C63B61000CE2D7C /* MessagingTest+EventFactory.m in Sources */, E9F7FE292BE0C61900699356 /* SetAllowGuestsAndServicesUseCaseTests.swift in Sources */, 16849B2023EDB32B00C025A8 /* MockSessionManager.swift in Sources */, 06E097A02A264F0300B38C4A /* ZMUserSessionTests+RecurringActions.swift in Sources */, 8766853C1F2A1AA00031081B /* UnauthenticatedSessionTests.swift in Sources */, 16D0A119234B999600A83F87 /* LabelUpstreamRequestStrategyTests.swift in Sources */, 169BA23D25EF6E2A00374343 /* MockRegistrationStatusDelegate.swift in Sources */, - 542DFEE81DDCA4FD000F5B95 /* UserProfileUpdateRequestStrategyTests.swift in Sources */, 7CE017152317D07E00144905 /* ZMAuthenticationStatusTests.swift in Sources */, 16085B351F719E6D000B9F22 /* NetworkStateRecorder.swift in Sources */, EE1DEBB323D5A6930087EE1F /* TypingTests.swift in Sources */, @@ -3142,18 +3078,15 @@ EE5BF6351F8F907C00B49D06 /* ZMLocalNotificationTests.swift in Sources */, 169BA25225EF778E00374343 /* TestUserProfileUpdateObserver.swift in Sources */, E9D6FC232CD4E92D007924B8 /* UpdateConversationFolderUseCaseTests.swift in Sources */, - BF50CFA71F39ACE8007833A4 /* MockUserInfoParser.swift in Sources */, 5E0EB1F92100A46F00B5DC2B /* MockCompanyLoginRequesterDelegate.swift in Sources */, 169BA24C25EF753100374343 /* MockReachability.swift in Sources */, 541228451AEE422C00D9ED1C /* ZMAuthenticationStatusTests.m in Sources */, 5E0EB1F72100A14A00B5DC2B /* CompanyLoginRequesterTests.swift in Sources */, 1671F9FF1E2FAF50009F3150 /* ZMLocalNotificationForTests_CallState.swift in Sources */, - EE6654642445D4EE00CBF8D3 /* MockAddressBook.swift in Sources */, 161ACB3A23F6BAFE00ABFF33 /* URLActionTests.swift in Sources */, 63CDCA792BD157E100DA0F0A /* CheckOneOnOneConversationIsReadyUseCaseTests.swift in Sources */, 16849B1C23EDA1FD00C025A8 /* MockUpdateEventProcessor.swift in Sources */, CBD3AED02C4A742B007643A0 /* TestSetup.swift in Sources */, - 545F601C1D6C336D00C2C55B /* AddressBookSearchTests.swift in Sources */, 162A81D6202B5BC600F6200C /* SessionManagerAVSTests.swift in Sources */, 169BA1F825ECF8F700374343 /* MockAppLock.swift in Sources */, 06F98D602437916B007E914A /* SignatureRequestStrategyTests.swift in Sources */, @@ -3181,7 +3114,6 @@ 3E1858BC1951D6DA005FE78F /* MemoryLeaksObserver.m in Sources */, F905C47F1E79A86A00AF34A5 /* WireCallCenterV3Tests.swift in Sources */, 1636EAFF23F6FCCC00CD9527 /* MockPresentationDelegate.swift in Sources */, - E6D1B1F22B988166001CA68B /* GetMLSFeatureUseCaseTests.swift in Sources */, 872C99601DB6722C006A3BDE /* CallKitDelegateTests+Mocking.m in Sources */, 061F791C2B767B3D00E8827B /* SelfClientCertificateProviderTests.swift in Sources */, 164C29A51ECF47D80026562A /* SearchDirectoryTests.swift in Sources */, @@ -3220,7 +3152,6 @@ 5E8BB8A52149130800EEA64B /* AVSBridgingTests.swift in Sources */, 161C887723FD4CFD00CB0B8E /* MockPushChannel.swift in Sources */, 59D5C2312C35846600CE1D5E /* AppendTextMessageUseCaseTests.swift in Sources */, - F9DAC54F1C2035660001F11E /* ConversationStatusStrategyTests.swift in Sources */, 164C29A31ECF437E0026562A /* SearchRequestTests.swift in Sources */, 168CF42F2007BCD9009FCB89 /* TeamInvitationStatusTests.swift in Sources */, EE8B934E2B838AFD00D5E670 /* GetE2eIdentityCertificatesUseCaseTests.swift in Sources */, @@ -3257,6 +3188,7 @@ F95ECF511B94BD05009F91BA /* ZMHotFixTests.m in Sources */, F9ABE8571EFD56BF00D83214 /* TeamDownloadRequestStrategy+EventsTests.swift in Sources */, 85D85EAFA1CB6E457D14E3B7 /* MockEntity2.m in Sources */, + 06185AA22CEBA6F6005D3CFE /* GetMLSFeatureUseCaseTests.swift in Sources */, 166D18A6230EC418001288CD /* MockMediaManager.swift in Sources */, 09914E531BD6613D00C10BF8 /* ZMDecodedAPSMessageTest.m in Sources */, 6349771E268B7C4300824A05 /* AVSVideoStreamsTest.swift in Sources */, @@ -3273,7 +3205,6 @@ 168CF42D2007BCA0009FCB89 /* TeamInvitationRequestStrategyTests.swift in Sources */, A97E4F5B267CFB2D006FC545 /* ZMUserSessionTests_NetworkState.swift in Sources */, EE01E0391F90FEC1001AA33C /* ZMLocalNotificationTests_ExpiredMessage.swift in Sources */, - 54C11BAD19D1EB7500576A96 /* ZMLoginTranscoderTests.m in Sources */, 16AD86B81F7292EB00E4C797 /* TypingChange.swift in Sources */, 16D3FCDF1E365ABC0052A535 /* CallStateObserverTests.swift in Sources */, 85D85EEDD5DD19FB747ED4A5 /* MockModelObjectContextFactory.m in Sources */, @@ -3282,7 +3213,6 @@ 0678D9942C2C5B54000DF6E3 /* CRLURLBuilderTests.swift in Sources */, F11E35D62040172200D4D5DB /* ZMHotFixTests.swift in Sources */, 1639A8542264C52600868AB9 /* ZMLocalNotificationTests_Alerts.swift in Sources */, - 545FC3341A5B003A005EEA26 /* ObjectTranscoderTests.m in Sources */, 5E9D32712109C54B0032FB06 /* CompanyLoginActionTests.swift in Sources */, 1836188BC0E48C1AC1671FC2 /* ZMSyncStrategyTests.swift in Sources */, 71AE6F20A2708DCF3BAD54F7 /* ZMOperationLoopTests.swift in Sources */, @@ -3326,6 +3256,7 @@ E9D6FC212CD4E496007924B8 /* UpdateConversationFolderUseCase.swift in Sources */, 634976E9268A185A00824A05 /* AVSVideoStreams.swift in Sources */, 5EC2C593213827BF00C6CE35 /* WireCallCenterV3+Events.swift in Sources */, + 768BC8992D071B7D00846CFF /* ZMUserSession+IndividualToTeamMigration.swift in Sources */, EE1DEBBE23D5E12F0087EE1F /* TypingUsersTimeout+Key.swift in Sources */, F1C1F3EE1FCF0C85007273E3 /* ZMUserSessionErrorCode+Localized.swift in Sources */, 166264742166093800300F45 /* CallEventStatus.swift in Sources */, @@ -3370,7 +3301,6 @@ EF2CB12722D5E58B00350B0A /* TeamImageAssetUpdateStrategy.swift in Sources */, E9F403172C5BD26C00F81366 /* SessionManager+AnalyticsUseCases.swift in Sources */, BF2ADA021F41A450000980E8 /* BackendEnvironmentProvider+Cookie.swift in Sources */, - EE1108B723D1B367005DC663 /* TypingUsers.swift in Sources */, 06B99C7B242B51A300FEAFDE /* SignatureRequestStrategy.swift in Sources */, 06E0979C2A261E5D00B38C4A /* ZMUserSession+RecurringAction.swift in Sources */, 5EFE9C15212AB138007932A6 /* UnregisteredUser+Payload.swift in Sources */, @@ -3452,6 +3382,7 @@ 16C4BDA020A309CD00BCDB17 /* CallParticipantSnapshot.swift in Sources */, 1639A8272260CE5000868AB9 /* ZMLocalNotification+AvailabilityAlert.swift in Sources */, EE8584DD2B6BD33B0045EAD4 /* ZMUserSession+OneOnOne.swift in Sources */, + 06FC48082CF0E36A00D31461 /* SearchUsersUseCase.swift in Sources */, 16ED865F23E3145C00CB1766 /* ZMUserSession+Clients.swift in Sources */, 06D16AAE2B9F3BC700D5A28A /* E2EIdentityCertificateUpdateStatusUseCase.swift in Sources */, 0640C26D26EA0B5C0057AF80 /* NSManagedObjectContext+Packaging.swift in Sources */, @@ -3500,6 +3431,7 @@ 63C4B3C22C35B07A00C09A93 /* FetchShareableConversationUseCase.swift in Sources */, 162DEE111F87B9800034C8F9 /* ZMUserSession+Calling.swift in Sources */, 161ACB2423F432CC00ABFF33 /* SessionManager+URLActions.swift in Sources */, + 06185AA02CEBA634005D3CFE /* GetMLSFeatureUseCase.swift in Sources */, BF3C1B1720DBE254001CE126 /* Conversation+MessageDestructionTimeout.swift in Sources */, 1659114F1DEF1F6E007FA847 /* LocalNotificationDispatcher+Calling.swift in Sources */, E623B63A2BD68DAE00F91B37 /* UserSessionDependencies.swift in Sources */, @@ -3537,13 +3469,11 @@ E6BD767F2BDBAAE300FB1F8B /* CoreDataStack+Caches.swift in Sources */, 54973A361DD48CAB007F8702 /* NSManagedObject+CryptoStack.swift in Sources */, 165D3A3D1E1D60520052E654 /* ZMConversation+VoiceChannel.swift in Sources */, - EE1DEBC723D5F1F30087EE1F /* NSManagedObjectContext+TypingUsers.swift in Sources */, EEE186B4259CC92D008707CA /* ZMUserSession+AppLock.swift in Sources */, 165D3A221E1D43870052E654 /* VoiceChannel.swift in Sources */, EE9CDE9027DA04A300C4DAC8 /* SessionManager+APIVersionResolver.swift in Sources */, 63E313D627553CA0002EAF1D /* ZMConversation+AVSIdentifier.swift in Sources */, 5EDF03EC2245563C00C04007 /* LinkPreviewAssetUploadRequestStrategy+Helper.swift in Sources */, - 54991D581DEDCF2B007E282F /* AddressBook.swift in Sources */, F96DBEEB1DF9A570008FE832 /* ZMSyncStrategy+ManagedObjectChanges.m in Sources */, EEE95CF62A442A0100E136CB /* WireCallCenterV3+MLS.swift in Sources */, 168CF4292007840A009FCB89 /* Team+Invite.swift in Sources */, @@ -3564,10 +3494,9 @@ 63C4B3C62C35B56A00C09A93 /* ShareFileUseCase.swift in Sources */, 1660AA0D1ECDB0250056D403 /* SearchTask.swift in Sources */, E9AD0A362C2AF9B300CA88DF /* AnalyticsServiceConfiguration.swift in Sources */, + 06FBF22F2CFD850700DA13EC /* UserSession+Federation.swift in Sources */, E91180E12CA6FD3E00DD63E2 /* ZMConversationMessage+SelfUserReactions.swift in Sources */, F9245BED1CBF95A8009D1E85 /* ZMHotFixDirectory+Swift.swift in Sources */, - E6C983EC2B98627200D55177 /* GetMLSFeatureUseCase.swift in Sources */, - 54991D5A1DEDD07E007E282F /* ContactAddressBook.swift in Sources */, EE0CAEAF2AAF2E8E00BD2DB7 /* URLSession+MinTLSVersion.swift in Sources */, E998FE8E2C184C9800EAA672 /* UserCredentials.swift in Sources */, 165BB94C2004D6490077EFD5 /* SessionManager+UserActivity.swift in Sources */, @@ -3592,14 +3521,12 @@ 161ACB3223F5BBA100ABFF33 /* CompanyLoginURLActionProcessor.swift in Sources */, 063AF985264B2DF800DCBCED /* CallClosedReason.swift in Sources */, EE9CDE8E27D9FF5900C4DAC8 /* APIVersionResolver.swift in Sources */, - 16F6BB381EDEA659009EA803 /* SearchResult+AddressBook.swift in Sources */, 5458AF841F7021B800E45977 /* PreLoginAuthenticationNotification.swift in Sources */, F9ABE84F1EFD568B00D83214 /* TeamDownloadRequestStrategy.swift in Sources */, EE38DDEE280437E500D4983D /* BlacklistReason.swift in Sources */, EE2DE5E229250C1A00F42F4C /* CallHandle.swift in Sources */, 871667FA1BB2AE9C009C6EEA /* APSSignalingKeysStore.swift in Sources */, 018F17B92B83B70A00E0594D /* AVSConversationType+Conference.swift in Sources */, - 54A343471D6B589A004B65EA /* AddressBookSearch.swift in Sources */, 5497100A1F6FFE9900026EDD /* ClientUpdateNotification.swift in Sources */, 16085B331F71811A000B9F22 /* UserChangeInfo+UserSession.swift in Sources */, 54131BC625C7F71400CE2CA2 /* LoginDelegate.swift in Sources */, @@ -4101,10 +4028,18 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ + 017F32C82D03AC3000471B3D /* WireAPI */ = { + isa = XCSwiftPackageProductDependency; + productName = WireAPI; + }; 01A532502CCA218A005FD421 /* WireAnalyticsSupport */ = { isa = XCSwiftPackageProductDependency; productName = WireAnalyticsSupport; }; + 59537D882CFF9E7700920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; 598D04352C89C6FB00B64D71 /* WireFoundation */ = { isa = XCSwiftPackageProductDependency; productName = WireFoundation; diff --git a/wire-ios-sync-engine/WireSyncEngine.xcodeproj/xcshareddata/xcschemes/WireSyncEngine.xcscheme b/wire-ios-sync-engine/WireSyncEngine.xcodeproj/xcshareddata/xcschemes/WireSyncEngine.xcscheme index da9909fd6c3..af0038aeabf 100644 --- a/wire-ios-sync-engine/WireSyncEngine.xcodeproj/xcshareddata/xcschemes/WireSyncEngine.xcscheme +++ b/wire-ios-sync-engine/WireSyncEngine.xcodeproj/xcshareddata/xcschemes/WireSyncEngine.xcscheme @@ -70,17 +70,6 @@ BlueprintName = "UnitTests" ReferencedContainer = "container:WireSyncEngine.xcodeproj"> - - - - - - - - @@ -91,11 +80,6 @@ BlueprintName = "IntegrationTests" ReferencedContainer = "container:WireSyncEngine.xcodeproj"> - - - - diff --git a/wire-ios-system/README.md b/wire-ios-system/README.md deleted file mode 100644 index d7b85e56014..00000000000 --- a/wire-ios-system/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Wire™ - -[![Wire logo](https://github.com/wireapp/wire/blob/master/assets/header-small.png?raw=true)](https://wire.com/jobs/) - -[![Azure Pipelines Build Status](https://dev.azure.com/wireswiss/Wire%20iOS/_apis/build/status/Frameworks/wire-ios-system?branchName=develop)](https://dev.azure.com/wireswiss/Wire%20iOS/_build/latest?definitionId=23&branchName=develop) [![codecov](https://codecov.io/gh/wireapp/wire-ios-system/branch/develop/graph/badge.svg)](https://codecov.io/gh/wireapp/wire-ios-system) - -This repository is part of the source code of Wire. You can find more information at [wire.com](https://wire.com) or by contacting opensource@wire.com. - -You can find the published source code at [github.com/wireapp/wire](https://github.com/wireapp/wire). - -For licensing information, see the attached LICENSE file and the list of third-party licenses at [wire.com/legal/licenses/](https://wire.com/legal/licenses/). - -# wire-ios-system - -This framework is part of Wire iOS. Additional documentation is available in the [Wire iOS wiki](https://github.com/wireapp/wire-ios/wiki). - -The wire-ios-system framework covers the interaction with OS Log, profiling and provides wrappers of some Foundation classes. diff --git a/WireFoundation/Sources/WireSystem/Cache.swift b/wire-ios-system/Source/Cache.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/Cache.swift rename to wire-ios-system/Source/Cache.swift diff --git a/WireFoundation/Sources/WireSystem/CircularArray.swift b/wire-ios-system/Source/CircularArray.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/CircularArray.swift rename to wire-ios-system/Source/CircularArray.swift diff --git a/WireFoundation/Sources/WireSystem/DateProviding/CurrentDateProviding.swift b/wire-ios-system/Source/DateProviding/CurrentDateProviding.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/DateProviding/CurrentDateProviding.swift rename to wire-ios-system/Source/DateProviding/CurrentDateProviding.swift diff --git a/WireFoundation/Sources/WireSystem/DateProviding/SystemDateProvider.swift b/wire-ios-system/Source/DateProviding/SystemDateProvider.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/DateProviding/SystemDateProvider.swift rename to wire-ios-system/Source/DateProviding/SystemDateProvider.swift diff --git a/WireFoundation/Sources/WireSystem/DispatchQueue+ZMSDispatchGroup.swift b/wire-ios-system/Source/DispatchQueue+ZMSDispatchGroup.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/DispatchQueue+ZMSDispatchGroup.swift rename to wire-ios-system/Source/DispatchQueue+ZMSDispatchGroup.swift diff --git a/WireFoundation/Sources/WireSystem/ExpiringActivity.swift b/wire-ios-system/Source/ExpiringActivity.swift similarity index 95% rename from WireFoundation/Sources/WireSystem/ExpiringActivity.swift rename to wire-ios-system/Source/ExpiringActivity.swift index 87cc24bf33a..4942ff3dc13 100644 --- a/WireFoundation/Sources/WireSystem/ExpiringActivity.swift +++ b/wire-ios-system/Source/ExpiringActivity.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging protocol ExpiringActivityInterface { @@ -47,14 +48,14 @@ public func withExpiringActivity(reason: String, block: @escaping () async throw actor ExpiringActivityManager { - let api: ExpiringActivityInterface - var task: Task? + let api: any ExpiringActivityInterface + var task: Task? init() { self.init(api: ProcessInfo.processInfo) } - init(api: ExpiringActivityInterface) { + init(api: any ExpiringActivityInterface) { self.api = api } @@ -90,7 +91,7 @@ actor ExpiringActivityManager { } } - func startWork(block: @escaping () async throws -> Void, semaphore: DispatchSemaphore) -> Task { + func startWork(block: @escaping () async throws -> Void, semaphore: DispatchSemaphore) -> Task { let task = Task { defer { WireLogger.backgroundActivity.debug("Releasing semaphore") diff --git a/WireFoundation/Sources/WireSystem/Extensions/Foundation/Error+SafeForLoggingStringConvertible.swift b/wire-ios-system/Source/Extensions/Foundation/Error+SafeForLoggingStringConvertible.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/Extensions/Foundation/Error+SafeForLoggingStringConvertible.swift rename to wire-ios-system/Source/Extensions/Foundation/Error+SafeForLoggingStringConvertible.swift diff --git a/WireFoundation/Sources/WireSystem/Extensions/Foundation/NSAttributedStringExtensions.swift b/wire-ios-system/Source/Extensions/Foundation/NSAttributedStringExtensions.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/Extensions/Foundation/NSAttributedStringExtensions.swift rename to wire-ios-system/Source/Extensions/Foundation/NSAttributedStringExtensions.swift diff --git a/WireFoundation/Sources/WireSystem/Extensions/UIKit/UIModalPresentationStyle+CustomDebugStringConvertible.swift b/wire-ios-system/Source/Extensions/UIKit/UIModalPresentationStyle+CustomDebugStringConvertible.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/Extensions/UIKit/UIModalPresentationStyle+CustomDebugStringConvertible.swift rename to wire-ios-system/Source/Extensions/UIKit/UIModalPresentationStyle+CustomDebugStringConvertible.swift diff --git a/WireFoundation/Sources/WireSystem/GroupQueue.swift b/wire-ios-system/Source/GroupQueue.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/GroupQueue.swift rename to wire-ios-system/Source/GroupQueue.swift diff --git a/WireFoundation/Sources/WireSystem/NavigationControllerDelegate/SupportedOrientationsDelegatingNavigationControllerDelegate.swift b/wire-ios-system/Source/NavigationControllerDelegate/SupportedOrientationsDelegatingNavigationControllerDelegate.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/NavigationControllerDelegate/SupportedOrientationsDelegatingNavigationControllerDelegate.swift rename to wire-ios-system/Source/NavigationControllerDelegate/SupportedOrientationsDelegatingNavigationControllerDelegate.swift diff --git a/WireFoundation/Sources/WireSystem/PopoverPresentationControllerConfiguration.swift b/wire-ios-system/Source/PopoverPresentationControllerConfiguration.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/PopoverPresentationControllerConfiguration.swift rename to wire-ios-system/Source/PopoverPresentationControllerConfiguration.swift diff --git a/WireFoundation/Sources/WireSystem/SafeForLoggingStringConvertible.swift b/wire-ios-system/Source/SafeForLoggingStringConvertible.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/SafeForLoggingStringConvertible.swift rename to wire-ios-system/Source/SafeForLoggingStringConvertible.swift diff --git a/WireFoundation/Sources/WireSystem/SanitizedString.swift b/wire-ios-system/Source/SanitizedString.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/SanitizedString.swift rename to wire-ios-system/Source/SanitizedString.swift diff --git a/WireFoundation/Sources/WireSystem/TimePoint.swift b/wire-ios-system/Source/TimePoint.swift similarity index 99% rename from WireFoundation/Sources/WireSystem/TimePoint.swift rename to wire-ios-system/Source/TimePoint.swift index 667f023b62a..adc6691adb6 100644 --- a/WireFoundation/Sources/WireSystem/TimePoint.swift +++ b/wire-ios-system/Source/TimePoint.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging /// Records the passage of time since its creation. It also stores the callstack at creation time. @objc(ZMSTimePoint) @objcMembers diff --git a/WireFoundation/Sources/WireSystem/UserDefaults+Temporary.swift b/wire-ios-system/Source/UserDefaults+Temporary.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/UserDefaults+Temporary.swift rename to wire-ios-system/Source/UserDefaults+Temporary.swift diff --git a/WireFoundation/Sources/WireSystem/Logging/WireLoggerObjc.swift b/wire-ios-system/Source/WireLoggerObjC.swift similarity index 91% rename from WireFoundation/Sources/WireSystem/Logging/WireLoggerObjc.swift rename to wire-ios-system/Source/WireLoggerObjC.swift index cc690c7f5e8..39cc63ffdee 100644 --- a/WireFoundation/Sources/WireSystem/Logging/WireLoggerObjc.swift +++ b/wire-ios-system/Source/WireLoggerObjC.swift @@ -17,10 +17,11 @@ // import Foundation +import WireLogging /// Class to proxy WireLogger methods to Objective-C @objcMembers -public final class WireLoggerObjc: NSObject { +public final class WireLoggerObjC: NSObject { static func assertionDumpLog(_ message: String) { WireLogger.system.critical(message, attributes: .safePublic) @@ -32,7 +33,7 @@ public final class WireLoggerObjc: NSObject { } @objc(logSaveCoreDataError:) - static func logSaveCoreData(error: Error) { + static func logSaveCoreData(error: any Error) { WireLogger.localStorage.error("Failed to save: \(error)", attributes: .safePublic) } } diff --git a/WireFoundation/Sources/WireSystem/ZMAssertionDumpFile.swift b/wire-ios-system/Source/ZMAssertionDumpFile.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/ZMAssertionDumpFile.swift rename to wire-ios-system/Source/ZMAssertionDumpFile.swift diff --git a/WireFoundation/Sources/WireSystem/ZMLogLevel.swift b/wire-ios-system/Source/ZMLogLevel.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/ZMLogLevel.swift rename to wire-ios-system/Source/ZMLogLevel.swift diff --git a/wire-ios-system/Source/ZMSAsserts.h b/wire-ios-system/Source/ZMSAsserts.h index 77f505dd782..90c5a246705 100644 --- a/wire-ios-system/Source/ZMSAsserts.h +++ b/wire-ios-system/Source/ZMSAsserts.h @@ -131,7 +131,7 @@ do { \ ""]; \ \ /* report error to datadog or other loggers */ \ - [WireLoggerObjc assertionDumpLog:output]; \ + [WireLoggerObjC assertionDumpLog:output]; \ \ /* prepare and dump to file */ \ [ZMAssertionDumpFile writeWithContent:output error:nil]; \ @@ -149,7 +149,7 @@ do { \ message]; \ \ /* report error to datadog or other loggers */ \ - [WireLoggerObjc assertionDumpLog:output]; \ + [WireLoggerObjC assertionDumpLog:output]; \ \ /* prepare and dump to file */ \ [ZMAssertionDumpFile writeWithContent:output error:nil]; \ diff --git a/WireFoundation/Sources/WireSystem/ZMSAsserts.swift b/wire-ios-system/Source/ZMSAsserts.swift similarity index 90% rename from WireFoundation/Sources/WireSystem/ZMSAsserts.swift rename to wire-ios-system/Source/ZMSAsserts.swift index bed44c9e836..11104d43742 100644 --- a/WireFoundation/Sources/WireSystem/ZMSAsserts.swift +++ b/wire-ios-system/Source/ZMSAsserts.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging /// Reports an error and terminates the application public func fatal( @@ -79,6 +80,15 @@ public enum AppBuild: UInt8 { } } +/// Reports an error and terminates the application if the current build is an internal build +public func fatalInternal( + _ message: String, + file: StaticString = #fileID, + line: UInt = #line +) { + requireInternal(false, message, file: file, line: line) +} + /// Terminates the application if the condition is `false` and the current build is not an AppStore build public func requireInternal( _ condition: Bool, diff --git a/WireFoundation/Sources/WireSystem/ZMSDispatchGroup.swift b/wire-ios-system/Source/ZMSDispatchGroup.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/ZMSDispatchGroup.swift rename to wire-ios-system/Source/ZMSDispatchGroup.swift diff --git a/WireFoundation/Sources/WireSystem/ZMSLog+Levels.swift b/wire-ios-system/Source/ZMSLog+Levels.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/ZMSLog+Levels.swift rename to wire-ios-system/Source/ZMSLog+Levels.swift diff --git a/WireFoundation/Sources/WireSystem/ZMSLog+Recording.swift b/wire-ios-system/Source/ZMSLog+Recording.swift similarity index 100% rename from WireFoundation/Sources/WireSystem/ZMSLog+Recording.swift rename to wire-ios-system/Source/ZMSLog+Recording.swift diff --git a/WireFoundation/Sources/WireSystem/ZMSLog.swift b/wire-ios-system/Source/ZMSLog.swift similarity index 99% rename from WireFoundation/Sources/WireSystem/ZMSLog.swift rename to wire-ios-system/Source/ZMSLog.swift index d67e7e0b3be..3c338371ce4 100644 --- a/WireFoundation/Sources/WireSystem/ZMSLog.swift +++ b/wire-ios-system/Source/ZMSLog.swift @@ -18,6 +18,7 @@ import Foundation import os.log +import WireLogging import ZipArchive /// Represents an entry to be logged. @@ -47,7 +48,7 @@ public final class ZMSLogEntry: NSObject { /// zmLog.warn("A serious warning!") /// @objc -public final class ZMSLog: NSObject { +public final class ZMSLog: NSObject, Sendable { public typealias LogHook = (_ level: ZMLogLevel, _ tag: String?, _ message: String) -> Void public typealias LogEntryHook = ( diff --git a/wire-ios-system/Support/Sourcery/config.yml b/wire-ios-system/Support/Sourcery/config.yml index c87a58da7f3..209a1893089 100644 --- a/wire-ios-system/Support/Sourcery/config.yml +++ b/wire-ios-system/Support/Sourcery/config.yml @@ -1,9 +1,9 @@ sources: - - ../../../WireFoundation/Sources/WireSystem + - ../../Source templates: - ./AutoMockable.stencil output: ./generated args: + autoMockableImports: ["Foundation"] autoMockableTestableImports: ["WireSystem"] - autoMockableImports: [] diff --git a/wire-ios-system/Support/Sourcery/generated/AutoMockable.generated.swift b/wire-ios-system/Support/Sourcery/generated/AutoMockable.generated.swift index b78b4ca7538..28331349258 100644 --- a/wire-ios-system/Support/Sourcery/generated/AutoMockable.generated.swift +++ b/wire-ios-system/Support/Sourcery/generated/AutoMockable.generated.swift @@ -24,13 +24,8 @@ // swiftlint:disable line_length // swiftlint:disable variable_name -public import Foundation -#if os(iOS) || os(tvOS) || os(watchOS) -public import UIKit -#elseif os(OSX) -public import AppKit -#endif +import Foundation @testable import WireSystem diff --git a/WireFoundation/Tests/WireSystemTests/CacheTests.swift b/wire-ios-system/Tests/CacheTests.swift similarity index 100% rename from WireFoundation/Tests/WireSystemTests/CacheTests.swift rename to wire-ios-system/Tests/CacheTests.swift diff --git a/WireFoundation/Tests/WireSystemTests/CircularArrayTests.swift b/wire-ios-system/Tests/CircularArrayTests.swift similarity index 100% rename from WireFoundation/Tests/WireSystemTests/CircularArrayTests.swift rename to wire-ios-system/Tests/CircularArrayTests.swift diff --git a/WireFoundation/Tests/WireSystemTests/DispatchGroupTests.swift b/wire-ios-system/Tests/DispatchGroupTests.swift similarity index 100% rename from WireFoundation/Tests/WireSystemTests/DispatchGroupTests.swift rename to wire-ios-system/Tests/DispatchGroupTests.swift diff --git a/WireFoundation/Tests/WireSystemTests/DispatchQueueHelperTests.swift b/wire-ios-system/Tests/DispatchQueueHelperTests.swift similarity index 100% rename from WireFoundation/Tests/WireSystemTests/DispatchQueueHelperTests.swift rename to wire-ios-system/Tests/DispatchQueueHelperTests.swift diff --git a/WireFoundation/Tests/WireSystemTests/ExpiringActivityTests.swift b/wire-ios-system/Tests/ExpiringActivityTests.swift similarity index 100% rename from WireFoundation/Tests/WireSystemTests/ExpiringActivityTests.swift rename to wire-ios-system/Tests/ExpiringActivityTests.swift diff --git a/WireFoundation/Tests/WireSystemTests/NavigationControllerDelegate/SupportedOrientationsDelegatingNavigationControllerDelegateTests.swift b/wire-ios-system/Tests/NavigationControllerDelegate/SupportedOrientationsDelegatingNavigationControllerDelegateTests.swift similarity index 100% rename from WireFoundation/Tests/WireSystemTests/NavigationControllerDelegate/SupportedOrientationsDelegatingNavigationControllerDelegateTests.swift rename to wire-ios-system/Tests/NavigationControllerDelegate/SupportedOrientationsDelegatingNavigationControllerDelegateTests.swift diff --git a/WireFoundation/Tests/WireSystemTests/PopoverPresentationControllerConfigurationTests.swift b/wire-ios-system/Tests/PopoverPresentationControllerConfigurationTests.swift similarity index 100% rename from WireFoundation/Tests/WireSystemTests/PopoverPresentationControllerConfigurationTests.swift rename to wire-ios-system/Tests/PopoverPresentationControllerConfigurationTests.swift diff --git a/WireFoundation/Tests/WireSystemTests/SanitizedStringTests.swift b/wire-ios-system/Tests/SanitizedStringTests.swift similarity index 100% rename from WireFoundation/Tests/WireSystemTests/SanitizedStringTests.swift rename to wire-ios-system/Tests/SanitizedStringTests.swift diff --git a/WireUI/.swiftpm/WireConversationListUI.xctestplan b/wire-ios-system/Tests/TestPlans/AllTests.xctestplan similarity index 53% rename from WireUI/.swiftpm/WireConversationListUI.xctestplan rename to wire-ios-system/Tests/TestPlans/AllTests.xctestplan index d878f895894..49625f077d0 100644 --- a/WireUI/.swiftpm/WireConversationListUI.xctestplan +++ b/wire-ios-system/Tests/TestPlans/AllTests.xctestplan @@ -1,8 +1,8 @@ { "configurations" : [ { - "id" : "2A84F21D-8220-4D29-844A-DAB7613C24E7", - "name" : "Test Scheme Action", + "id" : "828E0537-BD72-4A5E-85DC-229F6E68381E", + "name" : "Configuration 1", "options" : { } @@ -16,9 +16,9 @@ "testTargets" : [ { "target" : { - "containerPath" : "container:", - "identifier" : "WireConversationListUITests", - "name" : "WireConversationListUITests" + "containerPath" : "container:WireSystem.xcodeproj", + "identifier" : "54A3272B1B99A3190004EB95", + "name" : "WireSystem Tests" } } ], diff --git a/WireFoundation/Tests/WireSystemTests/TimePointTests.swift b/wire-ios-system/Tests/TimePointTests.swift similarity index 100% rename from WireFoundation/Tests/WireSystemTests/TimePointTests.swift rename to wire-ios-system/Tests/TimePointTests.swift diff --git a/WireFoundation/Tests/WireSystemTests/ZMAssertionDumpFileTests.swift b/wire-ios-system/Tests/ZMAssertionDumpFileTests.swift similarity index 100% rename from WireFoundation/Tests/WireSystemTests/ZMAssertionDumpFileTests.swift rename to wire-ios-system/Tests/ZMAssertionDumpFileTests.swift diff --git a/WireFoundation/Tests/WireSystemTests/ZMLogTests.swift b/wire-ios-system/Tests/ZMLogTests.swift similarity index 99% rename from WireFoundation/Tests/WireSystemTests/ZMLogTests.swift rename to wire-ios-system/Tests/ZMLogTests.swift index 6c86ffbe5f7..d9353efb806 100644 --- a/WireFoundation/Tests/WireSystemTests/ZMLogTests.swift +++ b/wire-ios-system/Tests/ZMLogTests.swift @@ -17,12 +17,12 @@ // import XCTest + @testable import WireSystem -class ZMLogTests: XCTestCase { +final class ZMLogTests: XCTestCase { override func setUp() { - super.setUp() ZMSLog.debug_resetAllLevels() ZMSLog.clearLogs() } @@ -31,7 +31,6 @@ class ZMLogTests: XCTestCase { ZMSLog.debug_resetAllLevels() ZMSLog.stopRecording() ZMSLog.removeAllLogHooks() - super.tearDown() } func testNumberOfPreviousZipLogURLs() { @@ -206,6 +205,7 @@ extension ZMLogTests { extension ZMLogTests { + @MainActor func testThatLogHookIsCalledWithError() { // GIVEN @@ -254,6 +254,7 @@ extension ZMLogTests { ZMSLog.removeLogHook(token: token) } + @MainActor func testThatLogHookIsCalledWithWarning() { // GIVEN @@ -303,6 +304,7 @@ extension ZMLogTests { ZMSLog.removeLogHook(token: token) } + @MainActor func testThatLogHookIsCalledWithDebugIfEnabled() { // GIVEN @@ -361,6 +363,7 @@ extension ZMLogTests { Thread.sleep(forTimeInterval: 0.2) } + @MainActor func testThatCallsMultipleLogHook() { // GIVEN diff --git a/WireFoundation/Sources/WireSystem/WireSystem.docc/WireSystem.md b/wire-ios-system/WireSystem.docc/WireSystem.md similarity index 100% rename from WireFoundation/Sources/WireSystem/WireSystem.docc/WireSystem.md rename to wire-ios-system/WireSystem.docc/WireSystem.md diff --git a/wire-ios-system/WireSystem.xcodeproj/project.pbxproj b/wire-ios-system/WireSystem.xcodeproj/project.pbxproj index c55ea9da900..dceac326995 100644 --- a/wire-ios-system/WireSystem.xcodeproj/project.pbxproj +++ b/wire-ios-system/WireSystem.xcodeproj/project.pbxproj @@ -11,38 +11,7 @@ 591B6E892C8B0A33009F8A7B /* ZipArchive.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = E6B58E402B21D8920046B6E1 /* ZipArchive.xcframework */; }; 591B6E8C2C8B0A37009F8A7B /* WireSystem.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3ECC35191AD436750089FD4B /* WireSystem.framework */; }; 591B6E8F2C8B0A3A009F8A7B /* WireSystem.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3ECC35191AD436750089FD4B /* WireSystem.framework */; }; - 5939CFCE2CECAF3D000F6FAD /* CacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939CFC02CECAF3D000F6FAD /* CacheTests.swift */; }; - 5939CFCF2CECAF3D000F6FAD /* CircularArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939CFC12CECAF3D000F6FAD /* CircularArrayTests.swift */; }; - 5939CFD02CECAF3D000F6FAD /* DispatchGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939CFC22CECAF3D000F6FAD /* DispatchGroupTests.swift */; }; - 5939CFD12CECAF3D000F6FAD /* DispatchQueueHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939CFC32CECAF3D000F6FAD /* DispatchQueueHelperTests.swift */; }; - 5939CFD22CECAF3D000F6FAD /* ExpiringActivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939CFC42CECAF3D000F6FAD /* ExpiringActivityTests.swift */; }; - 5939CFD32CECAF3D000F6FAD /* PopoverPresentationControllerConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939CFC52CECAF3D000F6FAD /* PopoverPresentationControllerConfigurationTests.swift */; }; - 5939CFD42CECAF3D000F6FAD /* SanitizedStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939CFC62CECAF3D000F6FAD /* SanitizedStringTests.swift */; }; - 5939CFD52CECAF3D000F6FAD /* TimePointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939CFC82CECAF3D000F6FAD /* TimePointTests.swift */; }; - 5939CFD62CECAF3D000F6FAD /* ZMAssertionDumpFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939CFC92CECAF3D000F6FAD /* ZMAssertionDumpFileTests.swift */; }; - 5939CFD72CECAF3D000F6FAD /* ZMDefinesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5939CFCA2CECAF3D000F6FAD /* ZMDefinesTest.m */; }; - 5939CFD82CECAF3D000F6FAD /* ZMLogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939CFCB2CECAF3D000F6FAD /* ZMLogTests.swift */; }; - 5939D12A2CECB0B5000F6FAD /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1042CECB0B5000F6FAD /* Cache.swift */; }; - 5939D12B2CECB0B5000F6FAD /* CircularArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1052CECB0B5000F6FAD /* CircularArray.swift */; }; - 5939D12C2CECB0B5000F6FAD /* DispatchQueue+ZMSDispatchGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1062CECB0B5000F6FAD /* DispatchQueue+ZMSDispatchGroup.swift */; }; - 5939D12D2CECB0B5000F6FAD /* ExpiringActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1072CECB0B5000F6FAD /* ExpiringActivity.swift */; }; - 5939D12E2CECB0B5000F6FAD /* GroupQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1082CECB0B5000F6FAD /* GroupQueue.swift */; }; - 5939D12F2CECB0B5000F6FAD /* PopoverPresentationControllerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1092CECB0B5000F6FAD /* PopoverPresentationControllerConfiguration.swift */; }; - 5939D1302CECB0B5000F6FAD /* SafeForLoggingStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D10A2CECB0B5000F6FAD /* SafeForLoggingStringConvertible.swift */; }; - 5939D1312CECB0B5000F6FAD /* SanitizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D10B2CECB0B5000F6FAD /* SanitizedString.swift */; }; - 5939D1322CECB0B5000F6FAD /* TimePoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D10C2CECB0B5000F6FAD /* TimePoint.swift */; }; - 5939D1332CECB0B5000F6FAD /* UserDefaults+Temporary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D10D2CECB0B5000F6FAD /* UserDefaults+Temporary.swift */; }; - 5939D1342CECB0B5000F6FAD /* ZMAssertionDumpFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D10F2CECB0B5000F6FAD /* ZMAssertionDumpFile.swift */; }; - 5939D1352CECB0B5000F6FAD /* ZMLogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1102CECB0B5000F6FAD /* ZMLogLevel.swift */; }; - 5939D1362CECB0B5000F6FAD /* ZMSAsserts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1122CECB0B5000F6FAD /* ZMSAsserts.swift */; }; - 5939D1372CECB0B5000F6FAD /* ZMSDispatchGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1142CECB0B5000F6FAD /* ZMSDispatchGroup.swift */; }; - 5939D1382CECB0B5000F6FAD /* ZMSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1152CECB0B5000F6FAD /* ZMSLog.swift */; }; - 5939D1392CECB0B5000F6FAD /* ZMSLog+Levels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1162CECB0B5000F6FAD /* ZMSLog+Levels.swift */; }; - 5939D13A2CECB0B5000F6FAD /* ZMSLog+Recording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5939D1172CECB0B5000F6FAD /* ZMSLog+Recording.swift */; }; - 5939D13B2CECB0B5000F6FAD /* WireSystem.h in Headers */ = {isa = PBXBuildFile; fileRef = 5939D10E2CECB0B5000F6FAD /* WireSystem.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5939D13C2CECB0B5000F6FAD /* ZMSAsserts.h in Headers */ = {isa = PBXBuildFile; fileRef = 5939D1112CECB0B5000F6FAD /* ZMSAsserts.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5939D13D2CECB0B5000F6FAD /* ZMSDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 5939D1132CECB0B5000F6FAD /* ZMSDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5939D13E2CECB0B5000F6FAD /* ZMSLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = 5939D1182CECB0B5000F6FAD /* ZMSLogging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5953781C2CFF42E900920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 5953781B2CFF42E900920B59 /* WireLogging */; }; 598E86ED2BF4DD3100FC5438 /* WireSystemSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 598E86D82BF4DB3700FC5438 /* WireSystemSupport.framework */; }; EE9AEC842BD1585500F7853F /* WireSystem.docc in Sources */ = {isa = PBXBuildFile; fileRef = EE9AEC832BD1585500F7853F /* WireSystem.docc */; }; /* End PBXBuildFile section */ @@ -74,43 +43,9 @@ /* Begin PBXFileReference section */ 3ECC35191AD436750089FD4B /* WireSystem.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WireSystem.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 54A3272C1B99A3190004EB95 /* WireSystem Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "WireSystem Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 54F6FD891B31BB5A000EC9BB /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - 5939CFC02CECAF3D000F6FAD /* CacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CacheTests.swift; path = ../../WireFoundation/Tests/WireSystemTests/CacheTests.swift; sourceTree = ""; }; - 5939CFC12CECAF3D000F6FAD /* CircularArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CircularArrayTests.swift; path = ../../WireFoundation/Tests/WireSystemTests/CircularArrayTests.swift; sourceTree = ""; }; - 5939CFC22CECAF3D000F6FAD /* DispatchGroupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DispatchGroupTests.swift; path = ../../WireFoundation/Tests/WireSystemTests/DispatchGroupTests.swift; sourceTree = ""; }; - 5939CFC32CECAF3D000F6FAD /* DispatchQueueHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DispatchQueueHelperTests.swift; path = ../../WireFoundation/Tests/WireSystemTests/DispatchQueueHelperTests.swift; sourceTree = ""; }; - 5939CFC42CECAF3D000F6FAD /* ExpiringActivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ExpiringActivityTests.swift; path = ../../WireFoundation/Tests/WireSystemTests/ExpiringActivityTests.swift; sourceTree = ""; }; - 5939CFC52CECAF3D000F6FAD /* PopoverPresentationControllerConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PopoverPresentationControllerConfigurationTests.swift; path = ../../WireFoundation/Tests/WireSystemTests/PopoverPresentationControllerConfigurationTests.swift; sourceTree = ""; }; - 5939CFC62CECAF3D000F6FAD /* SanitizedStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SanitizedStringTests.swift; path = ../../WireFoundation/Tests/WireSystemTests/SanitizedStringTests.swift; sourceTree = ""; }; - 5939CFC72CECAF3D000F6FAD /* Test-Bridging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Test-Bridging.h"; sourceTree = ""; }; - 5939CFC82CECAF3D000F6FAD /* TimePointTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TimePointTests.swift; path = ../../WireFoundation/Tests/WireSystemTests/TimePointTests.swift; sourceTree = ""; }; - 5939CFC92CECAF3D000F6FAD /* ZMAssertionDumpFileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZMAssertionDumpFileTests.swift; path = ../../WireFoundation/Tests/WireSystemTests/ZMAssertionDumpFileTests.swift; sourceTree = ""; }; - 5939CFCA2CECAF3D000F6FAD /* ZMDefinesTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ZMDefinesTest.m; sourceTree = ""; }; - 5939CFCB2CECAF3D000F6FAD /* ZMLogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZMLogTests.swift; path = ../../WireFoundation/Tests/WireSystemTests/ZMLogTests.swift; sourceTree = ""; }; - 5939D1042CECB0B5000F6FAD /* Cache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Cache.swift; path = ../../WireFoundation/Sources/WireSystem/Cache.swift; sourceTree = ""; }; - 5939D1052CECB0B5000F6FAD /* CircularArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CircularArray.swift; path = ../../WireFoundation/Sources/WireSystem/CircularArray.swift; sourceTree = ""; }; - 5939D1062CECB0B5000F6FAD /* DispatchQueue+ZMSDispatchGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "DispatchQueue+ZMSDispatchGroup.swift"; path = "../../WireFoundation/Sources/WireSystem/DispatchQueue+ZMSDispatchGroup.swift"; sourceTree = ""; }; - 5939D1072CECB0B5000F6FAD /* ExpiringActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ExpiringActivity.swift; path = ../../WireFoundation/Sources/WireSystem/ExpiringActivity.swift; sourceTree = ""; }; - 5939D1082CECB0B5000F6FAD /* GroupQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GroupQueue.swift; path = ../../WireFoundation/Sources/WireSystem/GroupQueue.swift; sourceTree = ""; }; - 5939D1092CECB0B5000F6FAD /* PopoverPresentationControllerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PopoverPresentationControllerConfiguration.swift; path = ../../WireFoundation/Sources/WireSystem/PopoverPresentationControllerConfiguration.swift; sourceTree = ""; }; - 5939D10A2CECB0B5000F6FAD /* SafeForLoggingStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SafeForLoggingStringConvertible.swift; path = ../../WireFoundation/Sources/WireSystem/SafeForLoggingStringConvertible.swift; sourceTree = ""; }; - 5939D10B2CECB0B5000F6FAD /* SanitizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SanitizedString.swift; path = ../../WireFoundation/Sources/WireSystem/SanitizedString.swift; sourceTree = ""; }; - 5939D10C2CECB0B5000F6FAD /* TimePoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TimePoint.swift; path = ../../WireFoundation/Sources/WireSystem/TimePoint.swift; sourceTree = ""; }; - 5939D10D2CECB0B5000F6FAD /* UserDefaults+Temporary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UserDefaults+Temporary.swift"; path = "../../WireFoundation/Sources/WireSystem/UserDefaults+Temporary.swift"; sourceTree = ""; }; - 5939D10E2CECB0B5000F6FAD /* WireSystem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WireSystem.h; sourceTree = ""; }; - 5939D10F2CECB0B5000F6FAD /* ZMAssertionDumpFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZMAssertionDumpFile.swift; path = ../../WireFoundation/Sources/WireSystem/ZMAssertionDumpFile.swift; sourceTree = ""; }; - 5939D1102CECB0B5000F6FAD /* ZMLogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZMLogLevel.swift; path = ../../WireFoundation/Sources/WireSystem/ZMLogLevel.swift; sourceTree = ""; }; - 5939D1112CECB0B5000F6FAD /* ZMSAsserts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZMSAsserts.h; sourceTree = ""; }; - 5939D1122CECB0B5000F6FAD /* ZMSAsserts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZMSAsserts.swift; path = ../../WireFoundation/Sources/WireSystem/ZMSAsserts.swift; sourceTree = ""; }; - 5939D1132CECB0B5000F6FAD /* ZMSDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZMSDefines.h; sourceTree = ""; }; - 5939D1142CECB0B5000F6FAD /* ZMSDispatchGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZMSDispatchGroup.swift; path = ../../WireFoundation/Sources/WireSystem/ZMSDispatchGroup.swift; sourceTree = ""; }; - 5939D1152CECB0B5000F6FAD /* ZMSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZMSLog.swift; path = ../../WireFoundation/Sources/WireSystem/ZMSLog.swift; sourceTree = ""; }; - 5939D1162CECB0B5000F6FAD /* ZMSLog+Levels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ZMSLog+Levels.swift"; path = "../../WireFoundation/Sources/WireSystem/ZMSLog+Levels.swift"; sourceTree = ""; }; - 5939D1172CECB0B5000F6FAD /* ZMSLog+Recording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ZMSLog+Recording.swift"; path = "../../WireFoundation/Sources/WireSystem/ZMSLog+Recording.swift"; sourceTree = ""; }; - 5939D1182CECB0B5000F6FAD /* ZMSLogging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZMSLogging.h; sourceTree = ""; }; 598E86D82BF4DB3700FC5438 /* WireSystemSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WireSystemSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E6B58E402B21D8920046B6E1 /* ZipArchive.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ZipArchive.xcframework; path = ../Carthage/Build/ZipArchive.xcframework; sourceTree = ""; }; - EE9AEC832BD1585500F7853F /* WireSystem.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; name = WireSystem.docc; path = ../WireFoundation/Sources/WireSystem/WireSystem.docc; sourceTree = ""; }; + EE9AEC832BD1585500F7853F /* WireSystem.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = WireSystem.docc; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -125,16 +60,30 @@ ); target = 598E86D72BF4DB3700FC5438 /* WireSystemSupport */; }; + 5953DD4A2CF0CA900069ABC8 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + publicHeaders = ( + WireSystem.h, + ZMSAsserts.h, + ZMSDefines.h, + ZMSLogging.h, + ); + target = 3ECC350F1AD436750089FD4B /* WireSystem */; + }; + 59D2643E2CF726150005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + TestPlans/AllTests.xctestplan, + ); + target = 54A3272B1B99A3190004EB95 /* WireSystem Tests */; + }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ 5939CB6D2CEBB29F000F6FAD /* Resources */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Resources; sourceTree = ""; }; 5939CB872CEBB2BA000F6FAD /* Support */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (5939CB8A2CEBB2BA000F6FAD /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Support; sourceTree = ""; }; - 5939CFDA2CECAF45000F6FAD /* NavigationControllerDelegate */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = NavigationControllerDelegate; path = ../../WireFoundation/Tests/WireSystemTests/NavigationControllerDelegate; sourceTree = ""; }; - 5939D1412CECB0BA000F6FAD /* DateProviding */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = DateProviding; path = ../../WireFoundation/Sources/WireSystem/DateProviding; sourceTree = ""; }; - 5939D1492CECB0BC000F6FAD /* Extensions */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = Extensions; path = ../../WireFoundation/Sources/WireSystem/Extensions; sourceTree = ""; }; - 5939D1572CECB0BE000F6FAD /* Logging */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = Logging; path = ../../WireFoundation/Sources/WireSystem/Logging; sourceTree = ""; }; - 5939D1632CECB0C0000F6FAD /* NavigationControllerDelegate */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); name = NavigationControllerDelegate; path = ../../WireFoundation/Sources/WireSystem/NavigationControllerDelegate; sourceTree = ""; }; + 5953DD242CF0CA900069ABC8 /* Source */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (5953DD4A2CF0CA900069ABC8 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Source; sourceTree = ""; }; + 5953DE452CF0CD2A0069ABC8 /* Tests */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (59D2643E2CF726150005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Tests; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -143,6 +92,7 @@ buildActionMask = 2147483647; files = ( 591B6E892C8B0A33009F8A7B /* ZipArchive.xcframework in Frameworks */, + 5953781C2CFF42E900920B59 /* WireLogging in Frameworks */, 013334BC2C204AB0002D97DB /* CocoaLumberjackSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -171,11 +121,10 @@ isa = PBXGroup; children = ( EE9AEC832BD1585500F7853F /* WireSystem.docc */, - 54F6FD891B31BB5A000EC9BB /* README.md */, 5939CB6D2CEBB29F000F6FAD /* Resources */, - 5939D1192CECB0B5000F6FAD /* Source */, + 5953DD242CF0CA900069ABC8 /* Source */, 5939CB872CEBB2BA000F6FAD /* Support */, - 5939CFCC2CECAF3D000F6FAD /* Tests */, + 5953DE452CF0CD2A0069ABC8 /* Tests */, 3ECC34E61AD433520089FD4B /* Products */, E66EC68A2B21D179003052F7 /* Frameworks */, ); @@ -195,58 +144,6 @@ name = Products; sourceTree = ""; }; - 5939CFCC2CECAF3D000F6FAD /* Tests */ = { - isa = PBXGroup; - children = ( - 5939CFDA2CECAF45000F6FAD /* NavigationControllerDelegate */, - 5939CFC02CECAF3D000F6FAD /* CacheTests.swift */, - 5939CFC12CECAF3D000F6FAD /* CircularArrayTests.swift */, - 5939CFC22CECAF3D000F6FAD /* DispatchGroupTests.swift */, - 5939CFC32CECAF3D000F6FAD /* DispatchQueueHelperTests.swift */, - 5939CFC42CECAF3D000F6FAD /* ExpiringActivityTests.swift */, - 5939CFC52CECAF3D000F6FAD /* PopoverPresentationControllerConfigurationTests.swift */, - 5939CFC62CECAF3D000F6FAD /* SanitizedStringTests.swift */, - 5939CFC72CECAF3D000F6FAD /* Test-Bridging.h */, - 5939CFC82CECAF3D000F6FAD /* TimePointTests.swift */, - 5939CFC92CECAF3D000F6FAD /* ZMAssertionDumpFileTests.swift */, - 5939CFCA2CECAF3D000F6FAD /* ZMDefinesTest.m */, - 5939CFCB2CECAF3D000F6FAD /* ZMLogTests.swift */, - ); - path = Tests; - sourceTree = ""; - }; - 5939D1192CECB0B5000F6FAD /* Source */ = { - isa = PBXGroup; - children = ( - 5939D1412CECB0BA000F6FAD /* DateProviding */, - 5939D1492CECB0BC000F6FAD /* Extensions */, - 5939D1572CECB0BE000F6FAD /* Logging */, - 5939D1632CECB0C0000F6FAD /* NavigationControllerDelegate */, - 5939D1042CECB0B5000F6FAD /* Cache.swift */, - 5939D1052CECB0B5000F6FAD /* CircularArray.swift */, - 5939D1062CECB0B5000F6FAD /* DispatchQueue+ZMSDispatchGroup.swift */, - 5939D1072CECB0B5000F6FAD /* ExpiringActivity.swift */, - 5939D1082CECB0B5000F6FAD /* GroupQueue.swift */, - 5939D1092CECB0B5000F6FAD /* PopoverPresentationControllerConfiguration.swift */, - 5939D10A2CECB0B5000F6FAD /* SafeForLoggingStringConvertible.swift */, - 5939D10B2CECB0B5000F6FAD /* SanitizedString.swift */, - 5939D10C2CECB0B5000F6FAD /* TimePoint.swift */, - 5939D10D2CECB0B5000F6FAD /* UserDefaults+Temporary.swift */, - 5939D10E2CECB0B5000F6FAD /* WireSystem.h */, - 5939D10F2CECB0B5000F6FAD /* ZMAssertionDumpFile.swift */, - 5939D1102CECB0B5000F6FAD /* ZMLogLevel.swift */, - 5939D1112CECB0B5000F6FAD /* ZMSAsserts.h */, - 5939D1122CECB0B5000F6FAD /* ZMSAsserts.swift */, - 5939D1132CECB0B5000F6FAD /* ZMSDefines.h */, - 5939D1142CECB0B5000F6FAD /* ZMSDispatchGroup.swift */, - 5939D1152CECB0B5000F6FAD /* ZMSLog.swift */, - 5939D1162CECB0B5000F6FAD /* ZMSLog+Levels.swift */, - 5939D1172CECB0B5000F6FAD /* ZMSLog+Recording.swift */, - 5939D1182CECB0B5000F6FAD /* ZMSLogging.h */, - ); - path = Source; - sourceTree = ""; - }; E66EC68A2B21D179003052F7 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -262,10 +159,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 5939D13B2CECB0B5000F6FAD /* WireSystem.h in Headers */, - 5939D13C2CECB0B5000F6FAD /* ZMSAsserts.h in Headers */, - 5939D13D2CECB0B5000F6FAD /* ZMSDefines.h in Headers */, - 5939D13E2CECB0B5000F6FAD /* ZMSLogging.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -294,14 +187,12 @@ dependencies = ( ); fileSystemSynchronizedGroups = ( - 5939D1412CECB0BA000F6FAD /* DateProviding */, - 5939D1492CECB0BC000F6FAD /* Extensions */, - 5939D1572CECB0BE000F6FAD /* Logging */, - 5939D1632CECB0C0000F6FAD /* NavigationControllerDelegate */, + 5953DD242CF0CA900069ABC8 /* Source */, ); name = WireSystem; packageProductDependencies = ( 013334BB2C204AB0002D97DB /* CocoaLumberjackSwift */, + 5953781B2CFF42E900920B59 /* WireLogging */, ); productName = SyncEngineSystem; productReference = 3ECC35191AD436750089FD4B /* WireSystem.framework */; @@ -322,7 +213,7 @@ 598E86F02BF4DD3100FC5438 /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( - 5939CFDA2CECAF45000F6FAD /* NavigationControllerDelegate */, + 5953DE452CF0CD2A0069ABC8 /* Tests */, ); name = "WireSystem Tests"; productName = "ZMCSystem-ios Tests"; @@ -460,23 +351,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5939D12A2CECB0B5000F6FAD /* Cache.swift in Sources */, - 5939D12B2CECB0B5000F6FAD /* CircularArray.swift in Sources */, - 5939D12C2CECB0B5000F6FAD /* DispatchQueue+ZMSDispatchGroup.swift in Sources */, - 5939D12D2CECB0B5000F6FAD /* ExpiringActivity.swift in Sources */, - 5939D12E2CECB0B5000F6FAD /* GroupQueue.swift in Sources */, - 5939D12F2CECB0B5000F6FAD /* PopoverPresentationControllerConfiguration.swift in Sources */, - 5939D1302CECB0B5000F6FAD /* SafeForLoggingStringConvertible.swift in Sources */, - 5939D1312CECB0B5000F6FAD /* SanitizedString.swift in Sources */, - 5939D1322CECB0B5000F6FAD /* TimePoint.swift in Sources */, - 5939D1332CECB0B5000F6FAD /* UserDefaults+Temporary.swift in Sources */, - 5939D1342CECB0B5000F6FAD /* ZMAssertionDumpFile.swift in Sources */, - 5939D1352CECB0B5000F6FAD /* ZMLogLevel.swift in Sources */, - 5939D1362CECB0B5000F6FAD /* ZMSAsserts.swift in Sources */, - 5939D1372CECB0B5000F6FAD /* ZMSDispatchGroup.swift in Sources */, - 5939D1382CECB0B5000F6FAD /* ZMSLog.swift in Sources */, - 5939D1392CECB0B5000F6FAD /* ZMSLog+Levels.swift in Sources */, - 5939D13A2CECB0B5000F6FAD /* ZMSLog+Recording.swift in Sources */, EE9AEC842BD1585500F7853F /* WireSystem.docc in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -485,17 +359,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5939CFCE2CECAF3D000F6FAD /* CacheTests.swift in Sources */, - 5939CFCF2CECAF3D000F6FAD /* CircularArrayTests.swift in Sources */, - 5939CFD02CECAF3D000F6FAD /* DispatchGroupTests.swift in Sources */, - 5939CFD12CECAF3D000F6FAD /* DispatchQueueHelperTests.swift in Sources */, - 5939CFD22CECAF3D000F6FAD /* ExpiringActivityTests.swift in Sources */, - 5939CFD32CECAF3D000F6FAD /* PopoverPresentationControllerConfigurationTests.swift in Sources */, - 5939CFD42CECAF3D000F6FAD /* SanitizedStringTests.swift in Sources */, - 5939CFD52CECAF3D000F6FAD /* TimePointTests.swift in Sources */, - 5939CFD62CECAF3D000F6FAD /* ZMAssertionDumpFileTests.swift in Sources */, - 5939CFD72CECAF3D000F6FAD /* ZMDefinesTest.m in Sources */, - 5939CFD82CECAF3D000F6FAD /* ZMLogTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -822,6 +685,10 @@ package = 013334BA2C204AB0002D97DB /* XCRemoteSwiftPackageReference "CocoaLumberjack" */; productName = CocoaLumberjackSwift; }; + 5953781B2CFF42E900920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 3ECC34C41AD430BB0089FD4B /* Project object */; diff --git a/wire-ios-system/WireSystem.xcodeproj/xcshareddata/xcschemes/WireSystem.xcscheme b/wire-ios-system/WireSystem.xcodeproj/xcshareddata/xcschemes/WireSystem.xcscheme index 75b704fc24d..faee892a3c5 100644 --- a/wire-ios-system/WireSystem.xcodeproj/xcshareddata/xcschemes/WireSystem.xcscheme +++ b/wire-ios-system/WireSystem.xcodeproj/xcshareddata/xcschemes/WireSystem.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1610" + version = "1.7"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> - - - - - - - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + - - - - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - -{one line to give the program's name and a brief idea of what it does.} -Copyright (C) {year} {name of author} - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -{project} Copyright (C) {year} {fullname} -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - -The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/wire-ios-testing/README.md b/wire-ios-testing/README.md deleted file mode 100644 index 7fe97917efc..00000000000 --- a/wire-ios-testing/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Wire™ -[![Wire logo](https://github.com/wireapp/wire/blob/master/assets/header-small.png?raw=true)](https://wire.com/jobs/) - -[![Azure Pipelines Build Status](https://dev.azure.com/wireswiss/Wire%20iOS/_apis/build/status/Frameworks/wire-ios-testing?branchName=develop)](https://dev.azure.com/wireswiss/Wire%20iOS/_build/latest?definitionId=22&branchName=develop) [![codecov](https://codecov.io/gh/wireapp/wire-ios-testing/branch/develop/graph/badge.svg)](https://codecov.io/gh/wireapp/wire-ios-testing) - - -This repository is part of the source code of Wire. You can find more information at [wire.com](https://wire.com) or by contacting opensource@wire.com. - -You can find the published source code at [github.com/wireapp/wire](https://github.com/wireapp/wire). - -For licensing information, see the attached LICENSE file and the list of third-party licenses at [wire.com/legal/licenses/](https://wire.com/legal/licenses/). - -# wire-ios-testing - -This framework is part of Wire iOS SyncEngine. Visit [iOS SyncEngine repository](http://github.com/wireapp/zmessaging-cocoa) for an overview of the architecture. - -The wire-ios-testing framwork compiles some utilities to help with unit / integration tests. - - -### How to build - -This framework is using Carthage to manage its dependencies. To pull the dependencies binaries, `run carthage bootstrap --platform ios`. - -You can now open the Xcode project and build. diff --git a/wire-ios-testing/Source/Public/XCTestCase+Helpers.swift b/wire-ios-testing/Source/Public/XCTestCase+Helpers.swift index b1d64c898f3..eaff1e9c048 100644 --- a/wire-ios-testing/Source/Public/XCTestCase+Helpers.swift +++ b/wire-ios-testing/Source/Public/XCTestCase+Helpers.swift @@ -50,6 +50,24 @@ public extension XCTestCase { typealias ThrowingBlock = () throws -> Void typealias EquatableError = Equatable & Error + func assertItThrows( + block: AsyncThrowingBlock, + errorHandler: (any Error) -> Void, + file: StaticString = #filePath, + line: UInt = #line + ) async { + do { + try await block() + XCTFail( + "No error was thrown", + file: file, + line: line + ) + } catch { + errorHandler(error) + } + } + func assertItThrows( error expectedError: some EquatableError, block: AsyncThrowingBlock, diff --git a/wire-ios-testing/Tests/TestPlans/AllTests.xctestplan b/wire-ios-testing/Tests/TestPlans/AllTests.xctestplan new file mode 100644 index 00000000000..7b686e86330 --- /dev/null +++ b/wire-ios-testing/Tests/TestPlans/AllTests.xctestplan @@ -0,0 +1,26 @@ +{ + "configurations" : [ + { + "id" : "90D290FF-BFDB-47A7-86A4-EB80D37A0CF4", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "language" : "en", + "region" : "DE", + "testExecutionOrdering" : "random" + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:WireTesting.xcodeproj", + "identifier" : "096AD7C51B68D41D00E007A8", + "name" : "WireTesting-Tests" + } + } + ], + "version" : 1 +} diff --git a/wire-ios-testing/WireTesting.xcodeproj/project.pbxproj b/wire-ios-testing/WireTesting.xcodeproj/project.pbxproj index f4a080e39e0..72dee874e1a 100644 --- a/wire-ios-testing/WireTesting.xcodeproj/project.pbxproj +++ b/wire-ios-testing/WireTesting.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -109,7 +109,6 @@ 54AFD7DF1CB698690090E33A /* not_animated.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = not_animated.gif; sourceTree = ""; }; 54AFD7E01CB698690090E33A /* tiny.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = tiny.jpg; sourceTree = ""; }; 54AFD7E71CB699710090E33A /* ZMTTestBaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZMTTestBaseTests.swift; sourceTree = ""; }; - 54BD49F11D41FA56006E29D1 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 595D4B1A2C09FA5800AF6269 /* XCTestCase+waitForPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+waitForPredicate.swift"; sourceTree = ""; }; 595D4B1F2C0A1A0500AF6269 /* XCTestExpectation+inverted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestExpectation+inverted.swift"; sourceTree = ""; }; 634CCB102BAC54FF00A11AB3 /* GenericArrayActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericArrayActor.swift; sourceTree = ""; }; @@ -126,6 +125,20 @@ F97E28BD1D8AACEA00A77567 /* UUID+WireTesting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UUID+WireTesting.swift"; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 59D2643C2CF725F90005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + AllTests.xctestplan, + ); + target = 096AD7C51B68D41D00E007A8 /* WireTesting-Tests */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 59D2643B2CF725F50005317F /* TestPlans */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (59D2643C2CF725F90005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TestPlans; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 096AD7B71B68D41D00E007A8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -151,7 +164,6 @@ isa = PBXGroup; children = ( EE9AEC852BD1589E00F7853F /* WireTesting.docc */, - 54BD49F11D41FA56006E29D1 /* README.md */, 096AD7DD1B68D6BD00E007A8 /* Source */, 096AD7E21B68D6BD00E007A8 /* Tests */, 096AD7DB1B68D6BD00E007A8 /* Resources */, @@ -201,6 +213,7 @@ 096AD7E21B68D6BD00E007A8 /* Tests */ = { isa = PBXGroup; children = ( + 59D2643B2CF725F50005317F /* TestPlans */, 096AD7E31B68D6BD00E007A8 /* Resources */, 096AD7E51B68D6BD00E007A8 /* WireTestingTests.m */, 5454E1491B73A09100F131F8 /* ZMTExpectationTests.swift */, @@ -359,6 +372,9 @@ dependencies = ( 096AD7C91B68D41D00E007A8 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 59D2643B2CF725F50005317F /* TestPlans */, + ); name = "WireTesting-Tests"; productName = ZMTestingTests; productReference = 096AD7C61B68D41D00E007A8 /* WireTesting-Tests.xctest */; diff --git a/wire-ios-testing/WireTesting.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wire-ios-testing/WireTesting.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 3ddf867a10a..dc8d12300fe 100644 --- a/wire-ios-testing/WireTesting.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/wire-ios-testing/WireTesting.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -4,5 +4,7 @@ BuildSystemType Latest + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + diff --git a/wire-ios-testing/WireTesting.xcodeproj/xcshareddata/xcschemes/WireTesting.xcscheme b/wire-ios-testing/WireTesting.xcodeproj/xcshareddata/xcschemes/WireTesting.xcscheme index 839adf48f1d..d884edac7fc 100644 --- a/wire-ios-testing/WireTesting.xcodeproj/xcshareddata/xcschemes/WireTesting.xcscheme +++ b/wire-ios-testing/WireTesting.xcodeproj/xcshareddata/xcschemes/WireTesting.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1610" + version = "1.7"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> - - - - + + + + @@ -59,15 +57,6 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - - - - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - -{one line to give the program's name and a brief idea of what it does.} -Copyright (C) {year} {name of author} - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -{project} Copyright (C) {year} {fullname} -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - -The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/wire-ios-transport/README.md b/wire-ios-transport/README.md deleted file mode 100644 index 2453e90a794..00000000000 --- a/wire-ios-transport/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Wire™ - -[![Wire logo](https://github.com/wireapp/wire/blob/master/assets/header-small.png?raw=true)](https://wire.com/jobs/) - -[![Build Status](https://dev.azure.com/wireswiss/Wire%20iOS/_apis/build/status/Frameworks/wire-ios-transport?branchName=develop)](https://dev.azure.com/wireswiss/Wire%20iOS/_build/latest?definitionId=19&branchName=develop) [![codecov](https://codecov.io/gh/wireapp/wire-ios-transport/branch/develop/graph/badge.svg)](https://codecov.io/gh/wireapp/wire-ios-transport) - - -This repository is part of the source code of Wire. You can find more information at [wire.com](https://wire.com) or by contacting opensource@wire.com. - -You can find the published source code at [github.com/wireapp/wire](https://github.com/wireapp/wire). - -For licensing information, see the attached LICENSE file and the list of third-party licenses at [wire.com/legal/licenses/](https://wire.com/legal/licenses/). - -# wire-ios-transport - -This framework is part of Wire iOS. Additional documentation is available in the [Wire iOS wiki](https://github.com/wireapp/wire-ios/wiki). - -The wire-ios-transport framework abstracts the network communication with our backend. It handles authentication of requests, network failures and retries transparently. - -### How to build - -This framework is using Carthage to manage its dependencies. To pull the dependencies binaries, run `carthage bootstrap --platform ios --use-xcframeworks`. - -You can now open the Xcode project and build. diff --git a/wire-ios-transport/Source/Authentication/ZMAccessTokenHandler.swift b/wire-ios-transport/Source/Authentication/ZMAccessTokenHandler.swift index 9e0fd5a30aa..2ad7c4c8735 100644 --- a/wire-ios-transport/Source/Authentication/ZMAccessTokenHandler.swift +++ b/wire-ios-transport/Source/Authentication/ZMAccessTokenHandler.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging @objc public extension ZMAccessTokenHandler { diff --git a/wire-ios-transport/Source/Background/BackgroundActivityFactory.swift b/wire-ios-transport/Source/Background/BackgroundActivityFactory.swift index 9f6ade90ad5..bdb0f83a429 100644 --- a/wire-ios-transport/Source/Background/BackgroundActivityFactory.swift +++ b/wire-ios-transport/Source/Background/BackgroundActivityFactory.swift @@ -17,6 +17,7 @@ // import UIKit +import WireLogging import WireUtilities /// Manages the creation and lifecycle of background tasks. diff --git a/wire-ios-transport/Source/Logging/RequestLog.swift b/wire-ios-transport/Source/Logging/RequestLog.swift index 3773033dbd0..889a74ec9ba 100644 --- a/wire-ios-transport/Source/Logging/RequestLog.swift +++ b/wire-ios-transport/Source/Logging/RequestLog.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging struct RequestLog: Codable { var method: String @@ -164,7 +165,7 @@ extension WireLogger { } } -extension WireLoggerObjc { +extension WireLoggerObjC { static func logRequest(_ request: NSURLRequest) { WireLogger.network.log(request: request) } diff --git a/wire-ios-transport/Source/Public/APIVersion.swift b/wire-ios-transport/Source/Public/APIVersion.swift index 1fddac09e03..690653ecc90 100644 --- a/wire-ios-transport/Source/Public/APIVersion.swift +++ b/wire-ios-transport/Source/Public/APIVersion.swift @@ -16,21 +16,18 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - /// Represents the backend API versions implemented by the client. @objc public enum APIVersion: Int32 { - - case v0 = 0 - case v1 = 1 - case v2 = 2 - case v3 = 3 - case v4 = 4 - case v5 = 5 - case v6 = 6 - + case v0 + case v1 + case v2 + case v3 + case v4 + case v5 + case v6 + case v7 } // MARK: - CaseIterable @@ -49,11 +46,6 @@ extension APIVersion: Comparable { public extension APIVersion { var useQualifiedIds: Bool { - switch self { - case .v0: - false - case .v1, .v2, .v3, .v4, .v5, .v6: - true - } + self != .v0 } } diff --git a/wire-ios-transport/Source/Public/BackendEnvironment.swift b/wire-ios-transport/Source/Public/BackendEnvironment.swift index fa9b7c83c78..a50afa48402 100644 --- a/wire-ios-transport/Source/Public/BackendEnvironment.swift +++ b/wire-ios-transport/Source/Public/BackendEnvironment.swift @@ -111,6 +111,8 @@ public extension EnvironmentType { public final class BackendEnvironment: NSObject { public let title: String + public let trustData: [TrustData] + let endpoints: BackendEndpointsProvider let proxySettings: ProxySettingsProvider? let certificateTrust: BackendTrustProvider @@ -118,6 +120,7 @@ public final class BackendEnvironment: NSObject { init( title: String, + trustData: [TrustData], environmentType: EnvironmentType, endpoints: BackendEndpointsProvider, proxySettings: ProxySettingsProvider?, @@ -128,6 +131,7 @@ public final class BackendEnvironment: NSObject { self.endpoints = endpoints self.proxySettings = proxySettings self.certificateTrust = certificateTrust + self.trustData = trustData } convenience init?(environmentType: EnvironmentType, data: Data) { @@ -146,6 +150,7 @@ public final class BackendEnvironment: NSObject { let certificateTrust = ServerCertificateTrust(trustData: pinnedKeys) self.init( title: backendData.title, + trustData: pinnedKeys, environmentType: environmentType, endpoints: backendData.endpoints, proxySettings: backendData.apiProxy, diff --git a/wire-ios-transport/Source/Public/BackendInfo.swift b/wire-ios-transport/Source/Public/BackendInfo.swift index 42df7d66be5..3b785df5fe4 100644 --- a/wire-ios-transport/Source/Public/BackendInfo.swift +++ b/wire-ios-transport/Source/Public/BackendInfo.swift @@ -26,6 +26,7 @@ public enum BackendInfo { case preferredAPIVersion = "PreferredAPIVersion" case domain = "Domain" case isFederationEnabled = "IsFederationEnabled" + case isMLSEnabled } @@ -66,6 +67,11 @@ public enum BackendInfo { set { storage.set(newValue, forKey: Key.isFederationEnabled.rawValue) } } + public static var isMLSEnabled: Bool { + get { storage.bool(forKey: Key.isMLSEnabled.rawValue) } + set { storage.set(newValue, forKey: Key.isMLSEnabled.rawValue) } + } + private static func apiVersion(for key: Key) -> APIVersion? { // Fetching an integer will default to 0 if no value exists for the key, // so explicitly check there is a value. diff --git a/wire-ios-transport/Source/Public/ServerCertificateTrust.swift b/wire-ios-transport/Source/Public/ServerCertificateTrust.swift index 1cd557c309c..69034ac7dee 100644 --- a/wire-ios-transport/Source/Public/ServerCertificateTrust.swift +++ b/wire-ios-transport/Source/Public/ServerCertificateTrust.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging final class ServerCertificateTrust: NSObject, BackendTrustProvider { let trustData: [TrustData] diff --git a/wire-ios-transport/Source/Public/TrustData.swift b/wire-ios-transport/Source/Public/TrustData.swift index fcac0a744f4..24f5783371b 100644 --- a/wire-ios-transport/Source/Public/TrustData.swift +++ b/wire-ios-transport/Source/Public/TrustData.swift @@ -18,19 +18,19 @@ import Foundation -struct TrustData: Decodable { - struct Host: Decodable { - enum Rule: String, Decodable { +public struct TrustData: Decodable { + public struct Host: Decodable { + public enum Rule: String, Decodable { case endsWith = "ends_with" case equals } - let rule: Rule - let value: String + public let rule: Rule + public let value: String } - let certificateKey: SecKey - let hosts: [Host] + public let certificateKey: SecKey + public let hosts: [Host] enum CodingKeys: String, CodingKey { case certificateKey diff --git a/wire-ios-transport/Source/Public/ZMUpdateEvent.swift b/wire-ios-transport/Source/Public/ZMUpdateEvent.swift index 2f7d0455ca2..12b004d09cc 100644 --- a/wire-ios-transport/Source/Public/ZMUpdateEvent.swift +++ b/wire-ios-transport/Source/Public/ZMUpdateEvent.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireUtilities @objc diff --git a/wire-ios-transport/Source/PushChannel/NativePushChannel.swift b/wire-ios-transport/Source/PushChannel/NativePushChannel.swift deleted file mode 100644 index 5a323af9fd4..00000000000 --- a/wire-ios-transport/Source/PushChannel/NativePushChannel.swift +++ /dev/null @@ -1,294 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Foundation - -@objcMembers -final class NativePushChannel: NSObject, PushChannelType { - - var clientID: String? { - didSet { - WireLogger.pushChannel.debug("Setting client ID") - scheduleOpen() - } - } - - var accessToken: AccessToken? { - didSet { - WireLogger.pushChannel.debug("Setting access token") - } - } - - var keepOpen: Bool = false { - didSet { - if keepOpen { - scheduleOpen() - } else { - close() - } - - } - } - - var canOpenConnection: Bool { - keepOpen && websocketURL != nil && consumer != nil - } - - let environment: BackendEnvironmentProvider - var session: URLSession? - let scheduler: ZMTransportRequestScheduler - var websocketTask: URLSessionWebSocketTask? - weak var consumer: ZMPushChannelConsumer? - var consumerQueue: GroupQueue? - var workQueue: OperationQueue - var pingTimer: ZMTimer? - private let minTLSVersion: TLSVersion - - required init( - scheduler: ZMTransportRequestScheduler, - userAgentString: String, - environment: BackendEnvironmentProvider, - proxyUsername: String?, - proxyPassword: String?, - minTLSVersion: String?, - queue: OperationQueue - ) { - self.environment = environment - self.scheduler = scheduler - self.workQueue = queue - self.proxyUsername = proxyUsername - self.proxyPassword = proxyPassword - self.minTLSVersion = TLSVersion.minVersionFrom(minTLSVersion) - super.init() - - let sessionConfig = URLSessionConfiguration.ephemeral - sessionConfig.tlsMinimumSupportedProtocolVersion = self.minTLSVersion.secValue - - if let settings = environment.proxy? - .socks5Settings(proxyUsername: proxyUsername, proxyPassword: proxyPassword) { - sessionConfig.httpShouldUsePipelining = true - sessionConfig.connectionProxyDictionary = settings - } - - self.session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: queue) - } - - func close() { - WireLogger.pushChannel.info("Push channel was closed") - - scheduler.performGroupedBlock { [weak self] in - self?.websocketTask?.cancel() - self?.onClose() - } - } - - func reachabilityDidChange(_ reachability: ReachabilityProvider) { - let didGoOnline = reachability.mayBeReachable && !reachability.oldMayBeReachable - guard didGoOnline else { return } - - scheduleOpen() - } - - func setPushChannelConsumer(_ consumer: ZMPushChannelConsumer?, queue: GroupQueue) { - consumerQueue = queue - self.consumer = consumer - - if consumer == nil { - close() - } else { - scheduleOpen() - } - } - - func open() { - guard - keepOpen, - websocketTask == nil, - let accessToken, - let websocketURL - else { - return - } - - var connectionRequest = URLRequest(url: websocketURL) - connectionRequest.setValue("\(accessToken.type) \(accessToken.token)", forHTTPHeaderField: "Authorization") - - websocketTask = session?.webSocketTask(with: connectionRequest) - websocketTask?.resume() - } - - func scheduleOpen() { - scheduler.performGroupedBlock { [weak self] in - self?.scheduleOpenInternal() - } - } - - private func scheduleOpenInternal() { - guard canOpenConnection else { - WireLogger.pushChannel.debug("Conditions for scheduling opening not fulfilled, waiting...") - return - } - WireLogger.pushChannel.debug("Schedule opening..") - scheduler.add(ZMOpenPushChannelRequest()) - } - - var websocketURL: URL? { - guard let clientID else { return nil } - - let url = environment.backendWSURL.appendingPathComponent("/await") - var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) - urlComponents?.queryItems = [URLQueryItem(name: "client", value: clientID)] - - return urlComponents?.url - } - - func listen() { - websocketTask?.receive(completionHandler: { [weak self] result in - switch result { - case let .failure(error): - WireLogger.pushChannel.debug("Failed to receive message \(error)") - self?.onClose() - case let .success(message): - guard case let .data(data) = message else { - break - } - - self?.consumerQueue?.performGroupedBlock { - self?.consumer?.pushChannelDidReceive(data) - } - } - - self?.listen() - }) - } - - // MARK: - Private - - private let proxyUsername: String? - private let proxyPassword: String? - - private func onClose() { - websocketTask = nil - stopPingTimer() - - consumerQueue?.performGroupedBlock { - self.consumer?.pushChannelDidClose() - } - - if keepOpen { - scheduleOpen() - } - } - - private func onOpen() { - listen() - startPingTimer() - - consumerQueue?.performGroupedBlock { - self.consumer?.pushChannelDidOpen() - } - } - - private func stopPingTimer() { - pingTimer?.cancel() - pingTimer = nil - } - - private func schedulePingTimer() { - stopPingTimer() - startPingTimer() - } - - private func startPingTimer() { - let timer = ZMTimer(target: self, operationQueue: workQueue) - timer?.fire(afterTimeInterval: 30) - pingTimer = timer - } - -} - -extension NativePushChannel: ZMTimerClient { - - func timerDidFire(_ timer: ZMTimer!) { - WireLogger.pushChannel.debug("Sending ping") - websocketTask?.sendPing(pongReceiveHandler: { error in - if let error { - WireLogger.pushChannel.debug("Failed to send ping: \(error)") - } - }) - schedulePingTimer() - } - -} - -extension NativePushChannel: URLSessionWebSocketDelegate { - - func urlSession( - _ session: URLSession, - webSocketTask: URLSessionWebSocketTask, - didOpenWithProtocol protocol: String? - ) { - WireLogger.pushChannel.info("Push channel did open with protocol \(`protocol` ?? "n/a")") - - onOpen() - } - - func urlSession( - _ session: URLSession, - webSocketTask: URLSessionWebSocketTask, - didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, - reason: Data? - ) { - WireLogger.pushChannel.info("Push channel did close with code \(closeCode), reason: \(reason ?? Data())") - - onClose() - } -} - -extension NativePushChannel: URLSessionDataDelegate { - - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - WireLogger.pushChannel - .error("Websocket open connection task did fail: \(error.map { String(describing: $0) } ?? "n/a")") - - websocketTask = nil - } - - func urlSession( - _ session: URLSession, - task: URLSessionTask, - didReceive challenge: URLAuthenticationChallenge, - completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void - ) { - - let protectionSpace = challenge.protectionSpace - - guard protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust else { - return completionHandler(.performDefaultHandling, challenge.proposedCredential) - } - - if let serverTrust = protectionSpace.serverTrust, - environment.verifyServerTrust(trust: serverTrust, host: protectionSpace.host) { - return completionHandler(.performDefaultHandling, challenge.proposedCredential) - } else { - return completionHandler(.cancelAuthenticationChallenge, nil) - } - - } - -} diff --git a/wire-ios-transport/Source/PushChannel/StarscreamPushChannel.swift b/wire-ios-transport/Source/PushChannel/StarscreamPushChannel.swift index a22ad820643..ad16ab03e1f 100644 --- a/wire-ios-transport/Source/PushChannel/StarscreamPushChannel.swift +++ b/wire-ios-transport/Source/PushChannel/StarscreamPushChannel.swift @@ -18,6 +18,7 @@ import Foundation import Starscream +import WireLogging @objcMembers final class StarscreamPushChannel: NSObject, PushChannelType { diff --git a/wire-ios-transport/Source/TransportSession/UnauthenticatedTransportSession.swift b/wire-ios-transport/Source/TransportSession/UnauthenticatedTransportSession.swift index 8c8d77ffad4..01ef5c6fa35 100644 --- a/wire-ios-transport/Source/TransportSession/UnauthenticatedTransportSession.swift +++ b/wire-ios-transport/Source/TransportSession/UnauthenticatedTransportSession.swift @@ -18,6 +18,7 @@ import CoreFoundation import Security +import WireLogging public enum EnqueueResult { case success diff --git a/wire-ios-transport/Source/TransportSession/ZMTransportSession.m b/wire-ios-transport/Source/TransportSession/ZMTransportSession.m index 2a3c182b51a..db2980ada26 100644 --- a/wire-ios-transport/Source/TransportSession/ZMTransportSession.m +++ b/wire-ios-transport/Source/TransportSession/ZMTransportSession.m @@ -238,7 +238,7 @@ - (instancetype)initWithURLSessionsDirectory:(id URLAuthenticationChallenge { - let protectionSpace = MockURLProtectionSpace( - host: "example.com", - port: 8080, - protocol: nil, - realm: nil, - authenticationMethod: authenticationMethod - ) - protectionSpace.mockServerTrust = SecTrust.trustWithChain(certificateData: certificates.production) - - return URLAuthenticationChallenge( - protectionSpace: protectionSpace, - proposedCredential: nil, - previousFailureCount: 0, - failureResponse: nil, - error: nil, - sender: MockURLAuthenticationChallengeSender() - ) - } - -} diff --git a/wire-ios-transport/Tests/Source/TransportSession/UnauthenticatedTransportSessionTests .swift b/wire-ios-transport/Tests/Source/TransportSession/UnauthenticatedTransportSessionTests .swift index a4d3f4ca6ff..db82bf43195 100644 --- a/wire-ios-transport/Tests/Source/TransportSession/UnauthenticatedTransportSessionTests .swift +++ b/wire-ios-transport/Tests/Source/TransportSession/UnauthenticatedTransportSessionTests .swift @@ -106,6 +106,7 @@ final class UnauthenticatedTransportSessionTests: ZMTBaseTest { let trust = MockCertificateTrust() let environment = BackendEnvironment( title: name, + trustData: [], environmentType: .production, endpoints: endpoints, proxySettings: nil, diff --git a/wire-ios-transport/Tests/Source/URLSession/BackendEnvironmentTests.swift b/wire-ios-transport/Tests/Source/URLSession/BackendEnvironmentTests.swift index 0c99d1b5478..7344a011cc4 100644 --- a/wire-ios-transport/Tests/Source/URLSession/BackendEnvironmentTests.swift +++ b/wire-ios-transport/Tests/Source/URLSession/BackendEnvironmentTests.swift @@ -69,6 +69,7 @@ class BackendEnvironmentTests: XCTestCase { let environmentType = EnvironmentType.custom(url: configURL) return BackendEnvironment( title: title, + trustData: [], environmentType: environmentType, endpoints: endpoints, proxySettings: proxySettings, diff --git a/wire-ios-transport/Tests/TestPlans/AllTests.xctestplan b/wire-ios-transport/Tests/TestPlans/AllTests.xctestplan new file mode 100644 index 00000000000..4d97aa1deaf --- /dev/null +++ b/wire-ios-transport/Tests/TestPlans/AllTests.xctestplan @@ -0,0 +1,26 @@ +{ + "configurations" : [ + { + "id" : "55198DBE-55D2-46E2-8EB2-94DDD0960AAB", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "language" : "en", + "region" : "DE", + "testExecutionOrdering" : "random" + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:WireTransport.xcodeproj", + "identifier" : "3E88BCB11B1F35DF00232589", + "name" : "WireTransport-ios-tests" + } + } + ], + "version" : 1 +} diff --git a/wire-ios-transport/WireTransport.xcodeproj/project.pbxproj b/wire-ios-transport/WireTransport.xcodeproj/project.pbxproj index 52c1c8e058b..997293c8a3e 100644 --- a/wire-ios-transport/WireTransport.xcodeproj/project.pbxproj +++ b/wire-ios-transport/WireTransport.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -21,11 +21,9 @@ 09F028C61B7252FC00BADDB6 /* Lorem Ipsum.txt in Resources */ = {isa = PBXBuildFile; fileRef = 09F028C51B7252FC00BADDB6 /* Lorem Ipsum.txt */; }; 160C31231E5B28A10012E4BC /* ZMPushChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 160C31221E5B28A10012E4BC /* ZMPushChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 161E04B42656C5FB00DADC3D /* ZMPushChannelType.h in Headers */ = {isa = PBXBuildFile; fileRef = 161E04B22656C3F600DADC3D /* ZMPushChannelType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 162F5806267150FC00337797 /* NativePushChannelTests+ServerTrust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 162F5805267150FC00337797 /* NativePushChannelTests+ServerTrust.swift */; }; 1650EBDD21CBFE9C00186075 /* MockURLAuthenticationChallengeSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1650EBDC21CBFE9C00186075 /* MockURLAuthenticationChallengeSender.swift */; }; 166D18BB230EE8A8001288CD /* TransportSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 166D18BA230EE8A8001288CD /* TransportSession.swift */; }; 1671F7562B6BFB5600C2D8A3 /* CookieProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1671F7552B6BFB5600C2D8A3 /* CookieProvider.swift */; }; - 16D7D9F7265251620049FFCA /* NativePushChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D7D9F6265251620049FFCA /* NativePushChannel.swift */; }; 16F900E226C2CBE6002794B5 /* StarscreamPushChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16F900E126C2CBE6002794B5 /* StarscreamPushChannel.swift */; }; 22D0F91C29027191004E2882 /* ProxySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22D0F91B29027191004E2882 /* ProxySettings.swift */; }; 5431ABFF1DB4C35B0040343C /* RequestLoopDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5431ABFE1DB4C35B0040343C /* RequestLoopDetection.swift */; }; @@ -103,6 +101,7 @@ 591B6E662C8B09F7009F8A7B /* WireTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E88BCA71B1F35DF00232589 /* WireTransport.framework */; }; 591B6E692C8B09FB009F8A7B /* WireTesting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE67F6ED296F074C001D7C88 /* WireTesting.framework */; }; 591B6E6C2C8B09FE009F8A7B /* WireTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E88BCA71B1F35DF00232589 /* WireTransport.framework */; }; + 59537CD92CFF7ED800920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59537CD82CFF7ED800920B59 /* WireLogging */; }; 596090D52B14C91D0007583F /* ZMUpdateEventTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596090D42B14C91D0007583F /* ZMUpdateEventTypeTests.swift */; }; 59FFFB762B74E100008F0E1B /* Date+TransportCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FFFB752B74E100008F0E1B /* Date+TransportCoding.swift */; }; 59FFFB782B74E17C008F0E1B /* TransportCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FFFB772B74E17C008F0E1B /* TransportCoding.swift */; }; @@ -230,11 +229,9 @@ 09F028C51B7252FC00BADDB6 /* Lorem Ipsum.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Lorem Ipsum.txt"; sourceTree = ""; }; 160C31221E5B28A10012E4BC /* ZMPushChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZMPushChannel.h; sourceTree = ""; }; 161E04B22656C3F600DADC3D /* ZMPushChannelType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZMPushChannelType.h; sourceTree = ""; }; - 162F5805267150FC00337797 /* NativePushChannelTests+ServerTrust.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NativePushChannelTests+ServerTrust.swift"; sourceTree = ""; }; 1650EBDC21CBFE9C00186075 /* MockURLAuthenticationChallengeSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockURLAuthenticationChallengeSender.swift; sourceTree = ""; }; 166D18BA230EE8A8001288CD /* TransportSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportSession.swift; sourceTree = ""; }; 1671F7552B6BFB5600C2D8A3 /* CookieProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieProvider.swift; sourceTree = ""; }; - 16D7D9F6265251620049FFCA /* NativePushChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativePushChannel.swift; sourceTree = ""; }; 16F900E126C2CBE6002794B5 /* StarscreamPushChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarscreamPushChannel.swift; sourceTree = ""; }; 22D0F91B29027191004E2882 /* ProxySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxySettings.swift; sourceTree = ""; }; 3E88BCA71B1F35DF00232589 /* WireTransport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WireTransport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -281,7 +278,6 @@ 5499FF7D1B31C66C00835C8B /* ZMWebSocketHandshake.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZMWebSocketHandshake.h; sourceTree = ""; }; 5499FF7E1B31C66C00835C8B /* ZMWebSocketHandshake.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMWebSocketHandshake.m; sourceTree = ""; }; 54B4D9E01E840C7E00F2892B /* NSURLSessionConfiguration+Debugging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSURLSessionConfiguration+Debugging.swift"; sourceTree = ""; }; - 54BD49FD1D41FE0B006E29D1 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 54C3EC5E243E2988000B9230 /* RequestAvailableNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestAvailableNotification.swift; sourceTree = ""; }; 54C3EC6E243E3516000B9230 /* RequestAvailableNotificationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestAvailableNotificationTests.swift; sourceTree = ""; }; 54CA15001B32F2C1008D3787 /* ZMAccessTokenHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZMAccessTokenHandler.h; sourceTree = ""; }; @@ -397,6 +393,20 @@ F9C4317D1CD7ADF300A8542F /* ZMKeychainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZMKeychainTests.m; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 59D264342CF7256A0005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + AllTests.xctestplan, + ); + target = 3E88BCB11B1F35DF00232589 /* WireTransport-ios-tests */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 59D264332CF725570005317F /* TestPlans */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (59D264342CF7256A0005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TestPlans; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 09A218631B790D330076547A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -412,6 +422,7 @@ buildActionMask = 2147483647; files = ( 591B6E632C8B09F3009F8A7B /* WireUtilities.framework in Frameworks */, + 59537CD92CFF7ED800920B59 /* WireLogging in Frameworks */, 01BBCFD82BF559AA001D9397 /* Starscream in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -475,7 +486,6 @@ children = ( EE9AEC8D2BD1590E00F7853F /* WireTransport.docc */, EF1A09A51FF6BEE300F89634 /* testing.modulemap */, - 54BD49FD1D41FE0B006E29D1 /* README.md */, 5499FF661B31C66C00835C8B /* Source */, 546E89BD1B33015000DD1042 /* Tests */, 3E88BD0B1B1F370700232589 /* Resources */, @@ -524,6 +534,7 @@ 546E89BD1B33015000DD1042 /* Tests */ = { isa = PBXGroup; children = ( + 59D264332CF725570005317F /* TestPlans */, F173092A21C7EC91008F82EE /* Resources */, 546E89BE1B33015000DD1042 /* Source */, ); @@ -579,7 +590,6 @@ 546E89CB1B33015000DD1042 /* ZMPushChannelConnectionTests.m */, 546E89CD1B33015000DD1042 /* ZMWebSocketHandshakeTests.m */, 546E89CE1B33015000DD1042 /* ZMWebSocketTests.m */, - 162F5805267150FC00337797 /* NativePushChannelTests+ServerTrust.swift */, ); path = PushChannel; sourceTree = ""; @@ -680,7 +690,6 @@ 5499FF7C1B31C66C00835C8B /* ZMWebSocketFrame.m */, 5499FF7D1B31C66C00835C8B /* ZMWebSocketHandshake.h */, 5499FF7E1B31C66C00835C8B /* ZMWebSocketHandshake.m */, - 16D7D9F6265251620049FFCA /* NativePushChannel.swift */, 16F900E126C2CBE6002794B5 /* StarscreamPushChannel.swift */, ); path = PushChannel; @@ -976,6 +985,7 @@ name = "WireTransport-ios"; packageProductDependencies = ( 01BBCFD72BF559AA001D9397 /* Starscream */, + 59537CD82CFF7ED800920B59 /* WireLogging */, ); productName = Transport; productReference = 3E88BCA71B1F35DF00232589 /* WireTransport.framework */; @@ -996,6 +1006,9 @@ 09A2188D1B790D6E0076547A /* PBXTargetDependency */, CB79AC6E2C6D00CF003CF5F4 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 59D264332CF725570005317F /* TestPlans */, + ); name = "WireTransport-ios-tests"; productName = TransportTests; productReference = 3E88BCB21B1F35DF00232589 /* WireTransport-ios-tests.xctest */; @@ -1153,7 +1166,6 @@ F19E555D22B27915005C792D /* ZMTransportResponse.swift in Sources */, 16F900E226C2CBE6002794B5 /* StarscreamPushChannel.swift in Sources */, CB79AC2D2C66613B003CF5F4 /* ZMPersistentCookieStorage.swift in Sources */, - 16D7D9F7265251620049FFCA /* NativePushChannel.swift in Sources */, 5451EFA01B7A138B002F503B /* Collections+ZMTSafeTypes.m in Sources */, F1D1281821C1556E0090045E /* BackendEnvironmentProvider.swift in Sources */, 54CA15AF1B32F2C1008D3787 /* ZMTaskIdentifierMap.m in Sources */, @@ -1217,7 +1229,6 @@ 01C83764292CC8DA009BB0BF /* ProxyCredentialsTests.swift in Sources */, 5E2C352621A567600034F1EE /* MockBackgroundActivityManager.swift in Sources */, 546E8A071B33015000DD1042 /* ZMURLSessionTests.m in Sources */, - 162F5806267150FC00337797 /* NativePushChannelTests+ServerTrust.swift in Sources */, 546E8A011B33015000DD1042 /* ZMTransportRequestSchedulerTests.m in Sources */, 54EA3E6D1BEA65B80071592B /* Fakes.m in Sources */, 546E8A031B33015000DD1042 /* ZMTransportSessionTests.m in Sources */, @@ -1632,6 +1643,10 @@ package = 01BBCFD62BF559AA001D9397 /* XCRemoteSwiftPackageReference "starscream" */; productName = Starscream; }; + 59537CD82CFF7ED800920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 3E88BC9E1B1F35DF00232589 /* Project object */; diff --git a/wire-ios-transport/WireTransport.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wire-ios-transport/WireTransport.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 3ddf867a10a..dc8d12300fe 100644 --- a/wire-ios-transport/WireTransport.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/wire-ios-transport/WireTransport.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -4,5 +4,7 @@ BuildSystemType Latest + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + diff --git a/wire-ios-transport/WireTransport.xcodeproj/xcshareddata/xcschemes/WireTransport.xcscheme b/wire-ios-transport/WireTransport.xcodeproj/xcshareddata/xcschemes/WireTransport.xcscheme index 0e30c4c00b0..28226e133ce 100644 --- a/wire-ios-transport/WireTransport.xcodeproj/xcshareddata/xcschemes/WireTransport.xcscheme +++ b/wire-ios-transport/WireTransport.xcodeproj/xcshareddata/xcschemes/WireTransport.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1610" + version = "1.7"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> - - - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/wire-ios-utilities/LICENSE b/wire-ios-utilities/LICENSE deleted file mode 100644 index ebd853e83a2..00000000000 --- a/wire-ios-utilities/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - -Copyright (C) 2007 Free Software Foundation, Inc. -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - -{one line to give the program's name and a brief idea of what it does.} -Copyright (C) {year} {name of author} - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -{project} Copyright (C) {year} {fullname} -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - -The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/wire-ios-utilities/README.md b/wire-ios-utilities/README.md deleted file mode 100644 index 7ce0d9e2a7d..00000000000 --- a/wire-ios-utilities/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Wire™ - -[![Wire logo](https://github.com/wireapp/wire/blob/master/assets/header-small.png?raw=true)](https://wire.com/jobs/) - -This repository is part of the source code of Wire. You can find more information at [wire.com](https://wire.com) or by contacting opensource@wire.com. - -You can find the published source code at [github.com/wireapp/wire](https://github.com/wireapp/wire). - -For licensing information, see the attached LICENSE file and the list of third-party licenses at [wire.com/legal/licenses/](https://wire.com/legal/licenses/). - -# WireUtilities - -This framework is part of Wire iOS. Additional documentation is available in the [Wire iOS wiki](https://github.com/wireapp/wire-ios/wiki). - -WireUtilities implements common data structures, algorithms (such as symmetric encryption) and application environment detection. - -### How to build - -This framework is using Carthage to manage its dependencies. To pull the dependencies binaries, run `carthage bootstrap --platform ios`. - -You can now open the Xcode project and build. diff --git a/wire-ios-utilities/Resources/WireUtilities-Info.plist b/wire-ios-utilities/Resources/WireUtilities-Info.plist index 13133fdf9b6..24c6c1ed69d 100644 --- a/wire-ios-utilities/Resources/WireUtilities-Info.plist +++ b/wire-ios-utilities/Resources/WireUtilities-Info.plist @@ -22,10 +22,6 @@ $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright Copyright © 2015 Wire. All rights reserved. - MLSEnabled - ${MLS_ENABLED} - CreateMLSGroupEnabled - ${CREATE_MLS_GROUP_ENABLED} ProteusByCoreCryptoEnabled ${PROTEUS_BY_CORECRYPTO_ENABLED} NSPrincipalClass diff --git a/wire-ios-utilities/Source/DeveloperFlag.swift b/wire-ios-utilities/Source/DeveloperFlag.swift index b7636957a0e..ae389202979 100644 --- a/wire-ios-utilities/Source/DeveloperFlag.swift +++ b/wire-ios-utilities/Source/DeveloperFlag.swift @@ -22,7 +22,6 @@ public enum DeveloperFlag: String, CaseIterable { public static var storage = UserDefaults.standard - case enableMLSSupport case showCreateMLSGroupToggle case proteusViaCoreCrypto case forceDatabaseLoadingFailure @@ -33,9 +32,6 @@ public enum DeveloperFlag: String, CaseIterable { public var description: String { switch self { - case .enableMLSSupport: - "Turn on to enable MLS support. This will cause the app to register an MLS client." - case .showCreateMLSGroupToggle: "Turn on to show the MLS toggle when creating a new group." @@ -84,10 +80,8 @@ public enum DeveloperFlag: String, CaseIterable { var bundleKey: String? { switch self { - case .enableMLSSupport: - "MLSEnabled" case .showCreateMLSGroupToggle: - "CreateMLSGroupEnabled" + nil case .proteusViaCoreCrypto: "ProteusByCoreCryptoEnabled" case .forceDatabaseLoadingFailure: diff --git a/wire-ios-utilities/Source/Keychain/Keychain.swift b/wire-ios-utilities/Source/Keychain/Keychain.swift index 6e8f063fcd5..a0e9cd763c9 100644 --- a/wire-ios-utilities/Source/Keychain/Keychain.swift +++ b/wire-ios-utilities/Source/Keychain/Keychain.swift @@ -19,6 +19,7 @@ import Foundation import LocalAuthentication import Security +import WireLogging public protocol KeychainItem { associatedtype Value diff --git a/wire-ios-utilities/Source/Public/String+Spaces.swift b/wire-ios-utilities/Source/Public/String+Spaces.swift index 0844049d138..8052a4efe98 100644 --- a/wire-ios-utilities/Source/Public/String+Spaces.swift +++ b/wire-ios-utilities/Source/Public/String+Spaces.swift @@ -24,4 +24,8 @@ public extension String { /// A standard non-breaking space ( ). static let nonBreakingSpace = "\u{00A0}" + + func trim() -> String { + trimmingCharacters(in: .whitespaces) + } } diff --git a/wire-ios-utilities/Support/Sourcery/config.yml b/wire-ios-utilities/Support/Sourcery/config.yml index 0b099a1b27d..a74a974a676 100644 --- a/wire-ios-utilities/Support/Sourcery/config.yml +++ b/wire-ios-utilities/Support/Sourcery/config.yml @@ -5,5 +5,5 @@ templates: output: ./generated args: - autoMockableTestableImports: ["WireUtilities"] autoMockableImports: ["UserNotifications"] + autoMockableTestableImports: ["WireUtilities"] diff --git a/wire-ios-utilities/Support/Sourcery/generated/AutoMockable.generated.swift b/wire-ios-utilities/Support/Sourcery/generated/AutoMockable.generated.swift index 04d234cc821..7a7c6784e99 100644 --- a/wire-ios-utilities/Support/Sourcery/generated/AutoMockable.generated.swift +++ b/wire-ios-utilities/Support/Sourcery/generated/AutoMockable.generated.swift @@ -24,14 +24,8 @@ // swiftlint:disable line_length // swiftlint:disable variable_name -public import Foundation -#if os(iOS) || os(tvOS) || os(watchOS) -public import UIKit -#elseif os(OSX) -public import AppKit -#endif - -public import UserNotifications + +import UserNotifications @testable import WireUtilities diff --git a/wire-ios-utilities/Tests/TestPlans/AllTests.xctestplan b/wire-ios-utilities/Tests/TestPlans/AllTests.xctestplan new file mode 100644 index 00000000000..c3dcc5e86bf --- /dev/null +++ b/wire-ios-utilities/Tests/TestPlans/AllTests.xctestplan @@ -0,0 +1,26 @@ +{ + "configurations" : [ + { + "id" : "754901A3-9CDC-4252-A3EE-01A168E7045C", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "language" : "en", + "region" : "DE", + "testExecutionOrdering" : "random" + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:WireUtilities.xcodeproj", + "identifier" : "3E88BD421B1F3EA300232589", + "name" : "WireUtilities-Tests" + } + } + ], + "version" : 1 +} diff --git a/wire-ios-utilities/WireUtilities.xcodeproj/project.pbxproj b/wire-ios-utilities/WireUtilities.xcodeproj/project.pbxproj index 6ad75148d2f..5298aeb0f4b 100644 --- a/wire-ios-utilities/WireUtilities.xcodeproj/project.pbxproj +++ b/wire-ios-utilities/WireUtilities.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -70,6 +70,7 @@ 591B6E7C2C8B0A1E009F8A7B /* WireUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E88BD381B1F3EA300232589 /* WireUtilities.framework */; }; 591B6E7E2C8B0A23009F8A7B /* WireTesting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE98A0AD296EFBE60055E981 /* WireTesting.framework */; }; 591B6E812C8B0A27009F8A7B /* WireUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E88BD381B1F3EA300232589 /* WireUtilities.framework */; }; + 59537CD72CFF7E1400920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59537CD62CFF7E1400920B59 /* WireLogging */; }; 5966D83E2BD6C20700305BBC /* AccentColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5966D83D2BD6C20700305BBC /* AccentColorTests.swift */; }; 5966D8432BD7DF3100305BBC /* ZMAccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5966D8422BD7DF3100305BBC /* ZMAccentColor.swift */; }; 596C4DCE2BE0F95D00F63E61 /* NSManagedObjectContext+GroupQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596C4DCD2BE0F95D00F63E61 /* NSManagedObjectContext+GroupQueue.swift */; }; @@ -249,7 +250,6 @@ 546E8A521B7501D0009EBA02 /* NSUUID+DataTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSUUID+DataTests.m"; path = "Source/NSUUID+DataTests.m"; sourceTree = ""; }; 546E8A581B750384009EBA02 /* NSManagedObjectContext+WireUtilitiesTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSManagedObjectContext+WireUtilitiesTests.m"; path = "Source/NSManagedObjectContext+WireUtilitiesTests.m"; sourceTree = ""; }; 546E8A611B75045E009EBA02 /* UnownedObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UnownedObjectTests.swift; path = Source/UnownedObjectTests.swift; sourceTree = ""; }; - 54BD49EE1D41FA1D006E29D1 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 54C1B0FB1B31A1E400CB8CF3 /* WireUtilities.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = WireUtilities.xcconfig; sourceTree = ""; }; 54C1B1001B31A1E400CB8CF3 /* version.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = version.xcconfig; sourceTree = ""; }; 54C388A71C4D5A3A00A55C79 /* NSUUID+Type1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSUUID+Type1.swift"; sourceTree = ""; }; @@ -263,8 +263,8 @@ 54CA31241B74F36B00B820C0 /* ZMFunctional.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZMFunctional.h; sourceTree = ""; }; 54CA31271B74F36B00B820C0 /* ZMTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZMTimer.h; sourceTree = ""; }; 54CA31281B74F36B00B820C0 /* WireUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WireUtilities.h; sourceTree = ""; }; - 54D06C581BC81976005F0FBB /* android_image.encrypted */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = android_image.encrypted; sourceTree = ""; }; - 54D06C5A1BC81983005F0FBB /* android_image.decrypted */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = android_image.decrypted; sourceTree = ""; }; + 54D06C581BC81976005F0FBB /* android_image.encrypted */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file; path = android_image.encrypted; sourceTree = ""; }; + 54D06C5A1BC81983005F0FBB /* android_image.decrypted */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file; path = android_image.decrypted; sourceTree = ""; }; 54FA8E811BC6CC3400E42980 /* NSData+ZMSCryptoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSData+ZMSCryptoTests.swift"; path = "Source/NSData+ZMSCryptoTests.swift"; sourceTree = ""; }; 5915B94F2BF4ACDC00215817 /* NativelySupportedUserDefaultsKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativelySupportedUserDefaultsKey.swift; sourceTree = ""; }; 5915B9512BF4AF2000215817 /* UserDefaults+NativelySupportedKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NativelySupportedKey.swift"; sourceTree = ""; }; @@ -379,6 +379,20 @@ F9D381D81B70DF8400E6E4EB /* WireUtilities-ios-tests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireUtilities-ios-tests-Bridging-Header.h"; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 59D2643A2CF725E00005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + AllTests.xctestplan, + ); + target = 3E88BD371B1F3EA300232589 /* WireUtilities */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 59D264392CF725D90005317F /* TestPlans */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (59D2643A2CF725E00005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TestPlans; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 09F028CB1B78BE2700BADDB6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -394,6 +408,7 @@ buildActionMask = 2147483647; files = ( 591B6E7A2C8B0A1B009F8A7B /* WireSystem.framework in Frameworks */, + 59537CD72CFF7E1400920B59 /* WireLogging in Frameworks */, 59B48C5A2C89CB1F00EA7999 /* WireFoundation in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -461,7 +476,6 @@ isa = PBXGroup; children = ( EE9AEC872BD158BC00F7853F /* WireUtilities.docc */, - 54BD49EE1D41FA1D006E29D1 /* README.md */, 3E88BD3A1B1F3EA300232589 /* Source */, 598E86B42BF4D64100FC5438 /* Support */, 3E88BD471B1F3EA300232589 /* Tests */, @@ -533,6 +547,7 @@ 3E88BD471B1F3EA300232589 /* Tests */ = { isa = PBXGroup; children = ( + 59D264392CF725D90005317F /* TestPlans */, 091799791B7E2F0300E60DD9 /* Resources */, F9D381A21B70B91000E6E4EB /* WireUtilities-Tests.pch */, F9D381D81B70DF8400E6E4EB /* WireUtilities-ios-tests-Bridging-Header.h */, @@ -874,9 +889,13 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 59D264392CF725D90005317F /* TestPlans */, + ); name = WireUtilities; packageProductDependencies = ( 59B48C592C89CB1F00EA7999 /* WireFoundation */, + 59537CD62CFF7E1400920B59 /* WireLogging */, ); productName = WireUtilities; productReference = 3E88BD381B1F3EA300232589 /* WireUtilities.framework */; @@ -1549,6 +1568,10 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ + 59537CD62CFF7E1400920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; 59B48C592C89CB1F00EA7999 /* WireFoundation */ = { isa = XCSwiftPackageProductDependency; productName = WireFoundation; diff --git a/wire-ios-utilities/WireUtilities.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wire-ios-utilities/WireUtilities.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 3ddf867a10a..dc8d12300fe 100644 --- a/wire-ios-utilities/WireUtilities.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/wire-ios-utilities/WireUtilities.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -4,5 +4,7 @@ BuildSystemType Latest + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + diff --git a/wire-ios-utilities/WireUtilities.xcodeproj/xcshareddata/xcschemes/WireUtilities.xcscheme b/wire-ios-utilities/WireUtilities.xcodeproj/xcshareddata/xcschemes/WireUtilities.xcscheme index 86d34d0f0dd..42abc9cb787 100644 --- a/wire-ios-utilities/WireUtilities.xcodeproj/xcshareddata/xcschemes/WireUtilities.xcscheme +++ b/wire-ios-utilities/WireUtilities.xcodeproj/xcshareddata/xcschemes/WireUtilities.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1610" + version = "1.7"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> - - - - - - - - - - - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + - - - - - - - - - - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - -{one line to give the program's name and a brief idea of what it does.} -Copyright (C) {year} {name of author} - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -{project} Copyright (C) {year} {fullname} -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - -The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/wire-ios-ziphy/README.md b/wire-ios-ziphy/README.md deleted file mode 100644 index 60fcd410823..00000000000 --- a/wire-ios-ziphy/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Wire™ - -![Wire logo](https://github.com/wireapp/wire/blob/master/assets/logo.png?raw=true) - -[![Azure Pipelines Build Status](https://dev.azure.com/wireswiss/Wire%20iOS/_apis/build/status/Frameworks/wire-ios-ziphy?branchName=develop)](https://dev.azure.com/wireswiss/Wire%20iOS/_build/latest?definitionId=27&branchName=develop) [![codecov](https://codecov.io/gh/wireapp/wire-ios-ziphy/branch/develop/graph/badge.svg)](https://codecov.io/gh/wireapp/wire-ios-ziphy) - -This repository is part of the source code of Wire. You can find more information at [wire.com](https://wire.com) or by contacting opensource@wire.com. - -You can find the published source code at [github.com/wireapp/wire](https://github.com/wireapp/wire). - -For licensing information, see the attached LICENSE file and the list of third-party licenses at [wire.com/legal/licenses/](https://wire.com/legal/licenses/). - -# wire-ios-ziphy - -This framework is part of the [Wire iOS client](http://github.com/wireapp/wire-ios). - -Ziphy is an helper framework for interacting with [Giphy](http://giphy.com). diff --git a/wire-ios-ziphy/Ziphy.docc/Ziphy.md b/wire-ios-ziphy/Ziphy.docc/Ziphy.md index dfc137f2d22..516b0dbaeed 100644 --- a/wire-ios-ziphy/Ziphy.docc/Ziphy.md +++ b/wire-ios-ziphy/Ziphy.docc/Ziphy.md @@ -6,4 +6,6 @@ Search and select animated GIFs on Giphy. Ziphy provides a user interface to search and select animaged GIFs on the Giphy platform. +![Wire logo](https://github.com/wireapp/wire/blob/master/assets/logo.png?raw=true) + ## Topics diff --git a/wire-ios-ziphy/Ziphy.xcodeproj/project.pbxproj b/wire-ios-ziphy/Ziphy.xcodeproj/project.pbxproj index f8386d7c085..bc0bafa0cae 100644 --- a/wire-ios-ziphy/Ziphy.xcodeproj/project.pbxproj +++ b/wire-ios-ziphy/Ziphy.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -97,6 +97,20 @@ EFEFAD1D206B8BF50046724E /* ZiphySearchResultsControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZiphySearchResultsControllerTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 59D2641E2CF7215E0005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + AllTests.xctestplan, + ); + target = DBF380631B15C0B800369FB7 /* ZiphyTests */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 59D2641D2CF721580005317F /* TestPlans */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (59D2641E2CF7215E0005317F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TestPlans; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ DBF380551B15C0B800369FB7 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -256,6 +270,7 @@ DBF380681B15C0B800369FB7 /* ZiphyTests */ = { isa = PBXGroup; children = ( + 59D2641D2CF721580005317F /* TestPlans */, 5EF5CDBB20F2005700CC98B0 /* MockResponses */, 5EF4A6D920F3A99F00A9EF22 /* Mocks */, 5EF5CDB720F1F99100CC98B0 /* ZiphyRequestGeneratorTests.swift */, @@ -321,6 +336,9 @@ dependencies = ( DBF380671B15C0B800369FB7 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 59D2641D2CF721580005317F /* TestPlans */, + ); name = ZiphyTests; productName = ziphyTests; productReference = DBF380641B15C0B800369FB7 /* ZiphyTests.xctest */; @@ -534,7 +552,8 @@ GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_SWIFT3_OBJC_INFERENCE = Off; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; diff --git a/wire-ios-ziphy/Ziphy.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wire-ios-ziphy/Ziphy.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 3ddf867a10a..dc8d12300fe 100644 --- a/wire-ios-ziphy/Ziphy.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/wire-ios-ziphy/Ziphy.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -4,5 +4,7 @@ BuildSystemType Latest + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + diff --git a/wire-ios-ziphy/Ziphy.xcodeproj/xcshareddata/xcschemes/Ziphy.xcscheme b/wire-ios-ziphy/Ziphy.xcodeproj/xcshareddata/xcschemes/Ziphy.xcscheme index c9266791014..8110befcbe9 100644 --- a/wire-ios-ziphy/Ziphy.xcodeproj/xcshareddata/xcschemes/Ziphy.xcscheme +++ b/wire-ios-ziphy/Ziphy.xcodeproj/xcshareddata/xcschemes/Ziphy.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1610" + version = "1.7"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + @@ -70,15 +57,6 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - - - - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - -{one line to give the program's name and a brief idea of what it does.} -Copyright (C) {year} {name of author} - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -{project} Copyright (C) {year} {fullname} -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - -The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/wire-ios/README.md b/wire-ios/README.md deleted file mode 100644 index a144d0fc1fd..00000000000 --- a/wire-ios/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Wire™ - -[![Wire logo](https://github.com/wireapp/wire/blob/master/assets/header-small.png?raw=true)](https://wire.com/jobs/) - -This repository is part of the source code of Wire. You can find more information at [wire.com](https://wire.com) or by contacting opensource@wire.com. - -You can find the published source code at [github.com/wireapp/wire](https://github.com/wireapp/wire). - -For licensing information, see the attached LICENSE file and the list of third-party licenses at [wire.com/legal/licenses/](https://wire.com/legal/licenses/). - -If you compile the open source software that we make available from time to time to develop your own mobile, desktop or web application, and cause that application to connect to our servers for any purposes, we refer to that resulting application as an “Open Source App”. All Open Source Apps are subject to, and may only be used and/or commercialized in accordance with, the Terms of Use applicable to the Wire Application, which can be found at https://wire.com/legal/#terms. Additionally, if you choose to build an Open Source App, certain restrictions apply, as follows: - -a. You agree not to change the way the Open Source App connects and interacts with our servers; b. You agree not to weaken any of the security features of the Open Source App; c. You agree not to use our servers to store data for purposes other than the intended and original functionality of the Open Source App; d. You acknowledge that you are solely responsible for any and all updates to your Open Source App. - -For clarity, if you compile the open source software that we make available from time to time to develop your own mobile, desktop or web application, and do not cause that application to connect to our servers for any purposes, then that application will not be deemed an Open Source App and the foregoing will not apply to that application. - -No license is granted to the Wire trademark and its associated logos, all of which will continue to be owned exclusively by Wire Swiss GmbH. Any use of the Wire trademark and/or its associated logos is expressly prohibited without the express prior written consent of Wire Swiss GmbH. - - -# Wire iOS - -The Wire mobile app has an architectural layer that we call *sync engine*. It is the client-side layer that processes all the data that is displayed in the mobile app. It handles network communication and authentication with the backend, push notifications, local caching of data, client-side business logic, signaling with the audio-video libraries, encryption and decryption (using encryption libraries from a lower level) and other bits and pieces. - -The user interface layer of the mobile app is built on top of the *sync engine*, which provides the data to display to the UI. -The sync engine itself is built on top of a few third-party frameworks, and uses Wire components that are shared between platforms for cryptography (Proteus/Cryptobox) and audio-video signaling (AVS). - -![Mobile app architecture](https://github.com/wireapp/wire/blob/master/assets/mobile-architecture.png?raw=true) - -### Documentation -Additional documentation is available in the [Wire iOS wiki](https://github.com/wireapp/wire-ios/wiki). - -## How to build the open source client - -### What is included in the open source client - -The project in this repository contains the Wire iOS client project. You can build the project yourself. However, there are some differences with the binary Wire iOS client available on the App Store. -These differences are: -- the open source project does not include the API keys of Vimeo, Localytics, HockeyApp and other 3rd party services. -- the open source project links against the open source Wire audio-video-signaling (AVS) library. The binary App Store client links against an AVS version that contains proprietary improvements for the call quality. - -### Prerequisites -In order to build Wire for iOS locally, it is necessary to install the following tools on the local machine: - -- macOS 11.6 (Big Sur) or newer -- Xcode 13.12.1 (https://itunes.apple.com/en/app/xcode/id497799835?mt=12). -- Carthage 0.38 or newer (https://github.com/Carthage/Carthage) - -The setup script will automatically check for you that you satisfy these requirements - -### How to build locally -1. Check out the wire-ios repository. -2. From the checkout folder, run `./setup.sh`. This will pull in all the necessary dependencies with Carthage and verify that you have the right version of the tools installed. -3. Open the project `Wire-iOS.xcodeproj` in Xcode -4. Click the "Run" button in Xcode - -These steps allow you to build only the Wire umbrella project, pulling in all other Wire frameworks with Carthage. If you want to modify the source/debug other Wire frameworks, you can open the `Carthage/Checkouts` subfolder and open the individual projects for each dependency there. - -You can then use `carthage bootstrap --platform ios --use-xcframeworks` to rebuild the dependency and use it in the umbrella project. - -### Known limitations - -Notifications send through Apple Push Notification service can only be received by the App Store Wire client, which is code signed with Wire's own certificate. This is a security feature enforced by Apple, as documented in Apple's [Local and Remote Notification Programming Guide](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/). Any client build from source will not be able to receive notifications. - -### Translations - -All Wire translations are crowdsourced via CrowdIn: https://crowdin.com/projects/wire diff --git a/wire-ios/Tests/Mocks/MockConverationDirectory.swift b/wire-ios/Tests/Mocks/MockConverationDirectory.swift index e465b896d1c..dfec409af2f 100644 --- a/wire-ios/Tests/Mocks/MockConverationDirectory.swift +++ b/wire-ios/Tests/Mocks/MockConverationDirectory.swift @@ -21,7 +21,7 @@ import WireDataModel class MockConversationDirectory: ConversationDirectoryType { - var allFolders: [LabelType] = [] + var nonDeletedFolders: [LabelType] = [] var mockGroupConversations: [ZMConversation] = [] var mockContactsConversations: [ZMConversation] = [] var mockFavoritesConversations: [ZMConversation] = [] diff --git a/wire-ios/Tests/Mocks/MockUser.h b/wire-ios/Tests/Mocks/MockUser.h index daf71894d28..8824a3ae06c 100644 --- a/wire-ios/Tests/Mocks/MockUser.h +++ b/wire-ios/Tests/Mocks/MockUser.h @@ -88,7 +88,6 @@ @property (nonatomic) NSSet > * clients; @property (nonatomic) ZMConnection *connection; -@property (nonatomic) ZMAddressBookContact *contact; @property (nonatomic) AddressBookEntry *addressBookEntry; @property (nonatomic, copy) NSUUID *remoteIdentifier; @property (nonatomic, readwrite) ZMAvailability availability; diff --git a/wire-ios/Tests/Mocks/UserSessionMock.swift b/wire-ios/Tests/Mocks/UserSessionMock.swift index 9edacc83fb0..b3b34f00b5e 100644 --- a/wire-ios/Tests/Mocks/UserSessionMock.swift +++ b/wire-ios/Tests/Mocks/UserSessionMock.swift @@ -341,6 +341,28 @@ final class UserSessionMock: UserSession { CreateConversationFolderUseCase(context: syncContext) } + func makeSearchUsersUseCase() -> SearchUsersUseCaseProtocol { + let mock = MockSearchUsersUseCaseProtocol() + mock.invokeQueryOptionsMessageProtocol_MockMethod = { _, _, _ in + let payload = ["documents": [ + [ + "id": self.selfUser.remoteIdentifier ?? UUID(), + "name": self.selfUser.name ?? "", + "accent_id": 1, + "handle": self.selfUser.handle ?? "" + ] + ]] + return SearchResult( + payload: payload, + query: .fullTextSearch(""), + searchOptions: [.directory], + contextProvider: MockContextProvider(), + searchUsersCache: nil + )! + } + return mock + } + var e2eiFeature: Feature.E2EI = .init(status: .enabled) var mlsFeature: Feature.MLS = .init( diff --git a/wire-ios/Tests/Sourcery/config.yml b/wire-ios/Tests/Sourcery/config.yml index d84641c16ee..87cc9ab2a65 100644 --- a/wire-ios/Tests/Sourcery/config.yml +++ b/wire-ios/Tests/Sourcery/config.yml @@ -6,5 +6,5 @@ templates: output: ./generated args: - autoMockableTestableImports: ["Wire", "WireCommonComponents"] autoMockableImports: ["CoreLocation", "WireDataModel", "WireSyncEngine"] + autoMockableTestableImports: ["Wire", "WireCommonComponents"] diff --git a/wire-ios/Tests/Sourcery/generated/AutoMockable.generated.swift b/wire-ios/Tests/Sourcery/generated/AutoMockable.generated.swift index 8a635cc047f..32c67175074 100644 --- a/wire-ios/Tests/Sourcery/generated/AutoMockable.generated.swift +++ b/wire-ios/Tests/Sourcery/generated/AutoMockable.generated.swift @@ -24,16 +24,10 @@ // swiftlint:disable line_length // swiftlint:disable variable_name -public import Foundation -#if os(iOS) || os(tvOS) || os(watchOS) -public import UIKit -#elseif os(OSX) -public import AppKit -#endif -public import CoreLocation -public import WireDataModel -public import WireSyncEngine +import CoreLocation +import WireDataModel +import WireSyncEngine @testable import Wire @testable import WireCommonComponents @@ -1428,20 +1422,20 @@ class MockSelfProfileViewControllerBuilderProtocol: SelfProfileViewControllerBui // MARK: - build - var build_Invocations: [Void] = [] - var build_MockMethod: (() -> UIViewController)? - var build_MockValue: UIViewController? + var buildMainCoordinator_Invocations: [AnyMainCoordinator] = [] + var buildMainCoordinator_MockMethod: ((AnyMainCoordinator) -> UIViewController)? + var buildMainCoordinator_MockValue: UIViewController? @MainActor - func build() -> UIViewController { - build_Invocations.append(()) + func build(mainCoordinator: AnyMainCoordinator) -> UIViewController { + buildMainCoordinator_Invocations.append(mainCoordinator) - if let mock = build_MockMethod { - return mock() - } else if let mock = build_MockValue { + if let mock = buildMainCoordinator_MockMethod { + return mock(mainCoordinator) + } else if let mock = buildMainCoordinator_MockValue { return mock } else { - fatalError("no mock for `build`") + fatalError("no mock for `buildMainCoordinator`") } } diff --git a/wire-ios/Wire Notification Service Extension/LegacyNotificationService.swift b/wire-ios/Wire Notification Service Extension/LegacyNotificationService.swift index b16b943a32b..c3feb2ab0d3 100644 --- a/wire-ios/Wire Notification Service Extension/LegacyNotificationService.swift +++ b/wire-ios/Wire Notification Service Extension/LegacyNotificationService.swift @@ -21,6 +21,7 @@ import UIKit import UserNotifications import WireCommonComponents import WireDataModel +import WireLogging import WireNotificationEngine import WireRequestStrategy import WireSyncEngine diff --git a/wire-ios/Wire Notification Service Extension/NotificationService.swift b/wire-ios/Wire Notification Service Extension/NotificationService.swift index 5325f79b2ae..ad82a60a0c9 100644 --- a/wire-ios/Wire Notification Service Extension/NotificationService.swift +++ b/wire-ios/Wire Notification Service Extension/NotificationService.swift @@ -19,6 +19,7 @@ import Foundation import UserNotifications import WireCommonComponents +import WireLogging import WireUtilities final class NotificationService: UNNotificationServiceExtension { @@ -29,7 +30,7 @@ final class NotificationService: UNNotificationServiceExtension { override init() { super.init() - WireAnalytics.Datadog.enable() + WireAnalytics.setup() } // MARK: - Methods diff --git a/wire-ios/Wire-iOS Share Extension/ShareExtensionViewController.swift b/wire-ios/Wire-iOS Share Extension/ShareExtensionViewController.swift index c6687003260..4e5c40ad293 100644 --- a/wire-ios/Wire-iOS Share Extension/ShareExtensionViewController.swift +++ b/wire-ios/Wire-iOS Share Extension/ShareExtensionViewController.swift @@ -26,6 +26,7 @@ import WireCoreCrypto import WireDataModel import WireDesign import WireLinkPreview +import WireLogging import WireShareEngine typealias Completion = () -> Void @@ -136,7 +137,7 @@ final class ShareExtensionViewController: SLComposeServiceViewController { } private func setUpDatadog() { - WireAnalytics.Datadog.enable() + WireAnalytics.setup() } override func viewDidLoad() { diff --git a/wire-ios/Wire-iOS Tests/AddParticipantsViewControllerSnapshotTests.swift b/wire-ios/Wire-iOS Tests/AddParticipantsViewControllerSnapshotTests.swift index 1acd0b65190..0ba512dbfde 100644 --- a/wire-ios/Wire-iOS Tests/AddParticipantsViewControllerSnapshotTests.swift +++ b/wire-ios/Wire-iOS Tests/AddParticipantsViewControllerSnapshotTests.swift @@ -59,6 +59,7 @@ final class AddParticipantsViewControllerSnapshotTests: XCTestCase { snapshotHelper = SnapshotHelper() SelfUser.setupMockSelfUser(inTeam: UUID()) mockSelfUser = SelfUser.provider?.providedSelfUser as? MockUserType + mockSelfUser.canCreateService = true userSession = UserSessionMock(mockUser: mockSelfUser) } @@ -106,10 +107,29 @@ final class AddParticipantsViewControllerSnapshotTests: XCTestCase { mockConversation.conversationType = .group mockConversation.teamType = MockTeam() mockConversation.allowServices = true + mockConversation.messageProtocol = .proteus sut = AddParticipantsViewController(context: .add(mockConversation), userSession: userSession) // THEN + XCTAssertTrue(mockConversation.botCanBeAdded) + snapshotHelper.verify(matching: sut) + } + + func testThatTabBarIsNotShown_WhenBotCanNotBeAdded() { + // GIVEN + let mockConversation = MockGroupDetailsConversation() + + // WHEN + mockConversation.conversationType = .group + mockConversation.teamType = MockTeam() + mockConversation.allowServices = true + mockConversation.messageProtocol = .mls + + sut = AddParticipantsViewController(context: .add(mockConversation), userSession: userSession) + + // THEN + XCTAssertFalse(mockConversation.botCanBeAdded) snapshotHelper.verify(matching: sut) } diff --git a/wire-ios/Wire-iOS Tests/ContactsCellSnapshotTests.swift b/wire-ios/Wire-iOS Tests/ContactsCellSnapshotTests.swift deleted file mode 100644 index 87c0fe4267c..00000000000 --- a/wire-ios/Wire-iOS Tests/ContactsCellSnapshotTests.swift +++ /dev/null @@ -1,156 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import WireCommonComponents -import WireTestingPackage -import XCTest - -@testable import Wire - -final class ContactsCellSnapshotTests: XCTestCase { - - // MARK: - Properties - - private var sut: ContactsCell! - private var snapshotHelper: SnapshotHelper! - - // MARK: - setUp - - override func setUp() { - super.setUp() - snapshotHelper = SnapshotHelper() - XCTestCase.accentColor = .blue - sut = ContactsCell() - } - - // MARK: - tearDown - - override func tearDown() { - snapshotHelper = nil - sut = nil - super.tearDown() - } - - // MARK: - Snapshot Tests - - func testForInviteButton() { - sut.user = SwiftMockLoader.mockUsers()[0] - sut.action = .invite - - snapshotHelper - .withUserInterfaceStyle(.light) - .verify( - matching: sut, - named: "LightTheme", - file: #filePath, - testName: #function, - line: #line - ) - - snapshotHelper - .withUserInterfaceStyle(.dark) - .verify( - matching: sut, - named: "DarkTheme", - file: #filePath, - testName: #function, - line: #line - ) - } - - func testForOpenButton() { - sut.user = SwiftMockLoader.mockUsers()[0] - sut.action = .open - - snapshotHelper - .withUserInterfaceStyle(.light) - .verify( - matching: sut, - named: "LightTheme", - file: #filePath, - testName: #function, - line: #line - ) - - snapshotHelper - .withUserInterfaceStyle(.dark) - .verify( - matching: sut, - named: "DarkTheme", - file: #filePath, - testName: #function, - line: #line - ) - } - - func testForOpenButtonWithALongUsername() { - let user = SwiftMockLoader.mockUsers()[0] - user.name = "A very long username which should be clipped at tail" - sut.user = user - sut.action = .open - - snapshotHelper - .withUserInterfaceStyle(.light) - .verify( - matching: sut, - named: "LightTheme", - file: #filePath, - testName: #function, - line: #line - ) - - snapshotHelper - .withUserInterfaceStyle(.dark) - .verify( - matching: sut, - named: "DarkTheme", - file: #filePath, - testName: #function, - line: #line - ) - } - - func testForNoSubtitle() { - let user = SwiftMockLoader.mockUsers()[0] - user.handle = nil - user.domain = nil - sut.user = user - sut.action = .open - - snapshotHelper - .withUserInterfaceStyle(.light) - .verify( - matching: sut, - named: "LightTheme", - file: #filePath, - testName: #function, - line: #line - ) - - snapshotHelper - .withUserInterfaceStyle(.dark) - .verify( - matching: sut, - named: "DarkTheme", - file: #filePath, - testName: #function, - line: #line - ) - } - -} diff --git a/wire-ios/Wire-iOS Tests/ContactsDataSourceTests.swift b/wire-ios/Wire-iOS Tests/ContactsDataSourceTests.swift deleted file mode 100644 index ebef6eed77b..00000000000 --- a/wire-ios/Wire-iOS Tests/ContactsDataSourceTests.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit -import XCTest -@testable import Wire - -final class ContactsDataSourceTests: XCTestCase { - - var dataSource: ContactsDataSource! - - override func setUp() { - super.setUp() - Thread.sleep(forTimeInterval: 0.5) - dataSource = ContactsDataSource() - } - - override func tearDown() { - dataSource = nil - super.tearDown() - } - - func testThatDataSourceHasCorrectNumberOfSectionsForSmallNumberOfUsers() { - // GIVEN - let mockUsers = SwiftMockLoader.mockUsers() - // WHEN - dataSource.ungroupedSearchResults = mockUsers - - // THEN - let sections: Int? = dataSource.numberOfSections(in: UITableView()) - XCTAssertFalse(dataSource.shouldShowSectionIndex) - XCTAssertEqual(sections, 1, "Number of sections must be 1") - } - - func testThatDataSourceHasCorrectNumberOfSectionsForLargeNumberOfUsers() { - // GIVEN - let mockUsers = SwiftMockLoader.mockUsers(fromResource: "a_lot_of_people.json") - - // WHEN - dataSource?.ungroupedSearchResults = mockUsers - - // THEN - let sections: Int? = dataSource.numberOfSections(in: UITableView()) - XCTAssertTrue(dataSource.shouldShowSectionIndex) - XCTAssertEqual(sections, 27, "Number of sections") - } - - func testThatDataSourceHasCorrectNumbersOfRowsInSectionsForLargeNumberOfUsers() { - // GIVEN - let mockUsers = SwiftMockLoader.mockUsers(fromResource: "a_lot_of_people.json") - - // WHEN - dataSource?.ungroupedSearchResults = mockUsers - - // THEN - let numberOfRawsInFirstSection: Int? = dataSource?.tableView(UITableView(), numberOfRowsInSection: 0) - XCTAssertEqual(numberOfRawsInFirstSection, 20, "") - - let numberOfSections: Int? = dataSource?.numberOfSections(in: UITableView()) - let numberOfRawsInLastSection: Int? = dataSource?.tableView( - UITableView(), - numberOfRowsInSection: (numberOfSections ?? 0) - 2 - ) - XCTAssertEqual(numberOfRawsInLastSection, 3, "") - } - - func testPerformanceExample() { - // GIVEN - let mockUsers = SwiftMockLoader.mockUsers(fromResource: "a_lot_of_people.json") - - measure { - // WHEN - self.dataSource?.ungroupedSearchResults = mockUsers - } - } - -} diff --git a/wire-ios/Wire-iOS Tests/ContactsViewControllerSnapshotTests.swift b/wire-ios/Wire-iOS Tests/ContactsViewControllerSnapshotTests.swift deleted file mode 100644 index c9ee03c36fe..00000000000 --- a/wire-ios/Wire-iOS Tests/ContactsViewControllerSnapshotTests.swift +++ /dev/null @@ -1,141 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import SnapshotTesting -import WireTestingPackage -import XCTest - -@testable import Wire - -final class ContactsViewControllerSnapshotTests: XCTestCase { - - // MARK: - Properties - - var sut: ContactsViewController! - var snapshotHelper: SnapshotHelper! - let imageConfig = ViewImageConfig.iPhone14(.portrait) - - // MARK: - setUp - - override func setUp() { - super.setUp() - snapshotHelper = SnapshotHelper() - .withUserInterfaceStyle(imageConfig.traits.userInterfaceStyle) - .withPreferredContentSizeCategory(imageConfig.traits.preferredContentSizeCategory) - - XCTestCase.accentColor = .blue - sut = ContactsViewController() - sut.searchHeaderViewController.overrideUserInterfaceStyle = .dark - sut.overrideUserInterfaceStyle = .dark - sut.view.backgroundColor = .black - } - - // MARK: - tearDown - - override func tearDown() { - snapshotHelper = nil - sut = nil - super.tearDown() - } - - // MARK: - Snapshot Tests - - func testForNoContacts() { - // Given - sut.dataSource.ungroupedSearchResults = [] - - // When - simulateSearch(withResults: false) - - // Then - wrapInNavigationController() - // see issue with KeyboardLayoudGuide: https://github.com/pointfreeco/swift-snapshot-testing/issues/859 - snapshotHelper.verify( - matching: sut, - size: imageConfig.size, - safeArea: imageConfig.safeArea - ) - } - - func testForNoSearchResult() { - // Given - sut.dataSource.searchQuery = "!!!" - - // When - simulateSearch(withResults: false) - - // Then - wrapInNavigationController() - // workaround for snapshot - // see issue with KeyboardLayoudGuide: https://github.com/pointfreeco/swift-snapshot-testing/issues/859 - snapshotHelper.verify( - matching: sut, - size: imageConfig.size, - safeArea: imageConfig.safeArea - ) - } - - func testForContactsWithoutSections() { - // Given - sut.dataSource.ungroupedSearchResults = SwiftMockLoader.mockUsers() - - // When - simulateSearch(withResults: true) - - // Then - wrapInNavigationController() - // workaround for snapshot - // see issue with KeyboardLayoudGuide: https://github.com/pointfreeco/swift-snapshot-testing/issues/859 - snapshotHelper.verify( - matching: sut, - size: imageConfig.size, - safeArea: imageConfig.safeArea - ) - } - - func testForContactsAndIndexSectionBarAreShown() { - // Given - let mockUsers = SwiftMockLoader.mockUsers(fromResource: "people-15Sections.json") - sut.dataSource.ungroupedSearchResults = mockUsers - - // When - simulateSearch(withResults: true) - - // Then - wrapInNavigationController() - // workaround for snapshot - // see issue with KeyboardLayoudGuide: https://github.com/pointfreeco/swift-snapshot-testing/issues/859 - snapshotHelper.verify( - matching: sut, - size: imageConfig.size, - safeArea: imageConfig.safeArea - ) - } - - // MARK: - Helper Methods - - private func simulateSearch(withResults: Bool) { - sut.updateEmptyResults(hasResults: withResults) - } - - private func wrapInNavigationController() { - let navigationController = UIViewController().wrapInNavigationController() - navigationController.pushViewController(sut, animated: false) - sut.tableView.reloadData() - } -} diff --git a/wire-ios/Wire-iOS Tests/ConversationCreationControllerSnapshotTests.swift b/wire-ios/Wire-iOS Tests/ConversationCreationControllerSnapshotTests.swift index 8518842defe..b5df938e60d 100644 --- a/wire-ios/Wire-iOS Tests/ConversationCreationControllerSnapshotTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationCreationControllerSnapshotTests.swift @@ -16,6 +16,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import WireAPI import WireTestingPackage import XCTest @@ -76,11 +77,28 @@ final class ConversationCreationControllerSnapshotTests: XCTestCase { ) } + func testTeamGroupOptions_withoutServices() { + createSut(isTeamMember: true, messageProtocol: .mls) + + snapshotHelper.verify(matching: sut) + } + // MARK: - Helper Method - private func createSut(isTeamMember: Bool) { + private func createSut(isTeamMember: Bool, messageProtocol: Feature.MLS.Config.MessageProtocol = .proteus) { let mockSelfUser = MockUserType.createSelfUser(name: "Alice", inTeam: isTeamMember ? UUID() : nil) let mockUserSession = UserSessionMock(mockUser: mockSelfUser) - sut = ConversationCreationController(preSelectedParticipants: nil, userSession: mockUserSession) + mockUserSession.mlsFeature = .init( + status: .enabled, + config: .init( + defaultProtocol: messageProtocol, + defaultCipherSuite: .MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 + ) + ) + + sut = ConversationCreationController( + preSelectedParticipants: nil, + userSession: mockUserSession + ) } } diff --git a/wire-ios/Wire-iOS Tests/ConversationDetail/GroupDetailsViewControllerSnapshotTests.swift b/wire-ios/Wire-iOS Tests/ConversationDetail/GroupDetailsViewControllerSnapshotTests.swift index 4370f7635f7..578238235d6 100644 --- a/wire-ios/Wire-iOS Tests/ConversationDetail/GroupDetailsViewControllerSnapshotTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationDetail/GroupDetailsViewControllerSnapshotTests.swift @@ -140,6 +140,7 @@ final class GroupDetailsViewControllerSnapshotTests: XCTestCase { mockSelfUser.canModifyAccessControlSettings = true createGroupConversation() + mockConversation.teamType = MockTeam() mockConversation.teamRemoteIdentifier = mockSelfUser.teamIdentifier mockConversation.allowGuests = true mockConversation.allowServices = true diff --git a/wire-ios/Wire-iOS Tests/ConversationList/Container/MockConversationListContainer.swift b/wire-ios/Wire-iOS Tests/ConversationList/Container/MockConversationListContainer.swift index d18f52c114f..157096845cb 100644 --- a/wire-ios/Wire-iOS Tests/ConversationList/Container/MockConversationListContainer.swift +++ b/wire-ios/Wire-iOS Tests/ConversationList/Container/MockConversationListContainer.swift @@ -111,4 +111,10 @@ final class MockConversationListContainer: UIViewController, ConversationListCon ) { // no-op } + + func conversationListViewControllerViewModelDidReloadContent( + _ viewModel: ConversationListViewController.ViewModel + ) { + // no - op + } } diff --git a/wire-ios/Wire-iOS Tests/ConversationList/ConversationListViewModel/ConversationListViewModelTests.swift b/wire-ios/Wire-iOS Tests/ConversationList/ConversationListViewModel/ConversationListViewModelTests.swift index 9ae477cd179..0013ae7e418 100644 --- a/wire-ios/Wire-iOS Tests/ConversationList/ConversationListViewModel/ConversationListViewModelTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationList/ConversationListViewModel/ConversationListViewModelTests.swift @@ -22,18 +22,10 @@ import XCTest @testable import Wire final class MockConversationListViewModelDelegate: NSObject, ConversationListViewModelDelegate { - func listViewModel(_ model: ConversationListViewModel?, didUpdateSection section: Int) { - // no-op - } - func listViewModel(_ model: ConversationListViewModel?, didUpdateSectionForReload section: Int, animated: Bool) { // no-op } - func listViewModel(_ model: ConversationListViewModel?, didChangeFolderEnabled folderEnabled: Bool) { - // no-op - } - func reload( using stagedChangeset: StagedChangeset, interrupt: ((Changeset) -> Bool)?, @@ -65,7 +57,6 @@ final class ConversationListViewModelTests: XCTestCase { override func setUp() { super.setUp() - removeViewModelState() mockUserSession = UserSessionMock() sut = ConversationListViewModel(userSession: mockUserSession) @@ -89,12 +80,6 @@ final class ConversationListViewModelTests: XCTestCase { super.tearDown() } - func removeViewModelState() { - guard let persistentURL = ConversationListViewModel.persistentURL else { return } - - try? FileManager.default.removeItem(at: persistentURL) - } - // 2 group conversations and 1 contact. First group conversation is mock conversation func fillDummyConversations(mockConversation: ZMConversation) { let info = ConversationDirectoryChangeInfo( @@ -115,7 +100,10 @@ final class ConversationListViewModelTests: XCTestCase { mockUserSession.mockConversationDirectory.mockGroupConversations = [mockConversation, teamConversation] mockUserSession.mockConversationDirectory.mockContactsConversations = [oneToOneConversation] - sut.conversationDirectoryDidChange(info) + sut.conversationDirectoryDidChange( + conversationDirectory: mockUserSession.mockConversationDirectory, + changeInfo: info + ) } func testForNumberOfItems() { diff --git a/wire-ios/Wire-iOS Tests/DeviceManagement/DeviceDetailsViewTests.swift b/wire-ios/Wire-iOS Tests/DeviceManagement/DeviceDetailsViewTests.swift index 9ce4102c73b..79f622f5c1a 100644 --- a/wire-ios/Wire-iOS Tests/DeviceManagement/DeviceDetailsViewTests.swift +++ b/wire-ios/Wire-iOS Tests/DeviceManagement/DeviceDetailsViewTests.swift @@ -154,8 +154,25 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { snapshotHelper.verify(matching: viewController) } + func testWhenE2eidentityIsDisabledAndMLSIsEnabled() { + client.e2eIdentityCertificate = nil + client.mlsThumbPrint = E2eIdentityCertificate.mockValid.mlsThumbprint + + let viewModel = prepareViewModel( + isProteusVerificationEnabled: true, + isE2eIdentityEnabled: true, + proteusKeyFingerPrint: mockFingerPrint, + isSelfClient: false + ) + + let viewController = setupWrappedInNavigationController(viewModel: viewModel) + + snapshotHelper.verify(matching: viewController) + } + func testWhenE2eidentityViewIsEnabledAndCertificateIsValid() { client.e2eIdentityCertificate = .mockValid + client.mlsThumbPrint = E2eIdentityCertificate.mockValid.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -171,6 +188,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityViewIsEnabledAndCertificateIsValidWhenProteusIsNotVerifiedThenBlueShieldIsNotShown() { client.e2eIdentityCertificate = .mockValid + client.mlsThumbPrint = E2eIdentityCertificate.mockValid.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: false, @@ -186,6 +204,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityViewIsEnabledAndCertificateIsRevoked() { client.e2eIdentityCertificate = .mockRevoked + client.mlsThumbPrint = E2eIdentityCertificate.mockRevoked.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -201,6 +220,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityViewIsEnabledAndCertificateIsExpired() { client.e2eIdentityCertificate = .mockExpired + client.mlsThumbPrint = E2eIdentityCertificate.mockExpired.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -216,6 +236,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityViewIsEnabledAndCertificateIsNotActivated() { client.e2eIdentityCertificate = .mockNotActivated + client.mlsThumbPrint = E2eIdentityCertificate.mockNotActivated.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -231,6 +252,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityIsEnabledAndCertificateIsExpiredForOtherClient() { client.e2eIdentityCertificate = .mockExpired + client.mlsThumbPrint = E2eIdentityCertificate.mockExpired.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -246,6 +268,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityIsEnabledAndCertificateIsNotActivatedForOtherClient() { client.e2eIdentityCertificate = .mockNotActivated + client.mlsThumbPrint = E2eIdentityCertificate.mockNotActivated.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -261,6 +284,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityViewIsEnabledAndCertificateIsInvalid() { client.e2eIdentityCertificate = .mockInvalid + client.mlsThumbPrint = E2eIdentityCertificate.mockInvalid.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -312,6 +336,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityViewIsEnabledAndCertificateIsValidInDarkMode() { client.e2eIdentityCertificate = .mockValid + client.mlsThumbPrint = E2eIdentityCertificate.mockValid.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -329,6 +354,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityViewIsEnabledAndCertificateIsRevokedInDarkMode() { client.e2eIdentityCertificate = .mockRevoked + client.mlsThumbPrint = E2eIdentityCertificate.mockRevoked.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -346,6 +372,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityViewIsEnabledAndCertificateIsExpiredInDarkMode() { client.e2eIdentityCertificate = .mockExpired + client.mlsThumbPrint = E2eIdentityCertificate.mockExpired.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -363,6 +390,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityViewIsEnabledAndCertificateIsNotActivatedInDarkMode() { client.e2eIdentityCertificate = .mockNotActivated + client.mlsThumbPrint = E2eIdentityCertificate.mockNotActivated.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -380,6 +408,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityViewIsEnabledAndCertificateIsExpiredInDarkModeForOtherClient() { client.e2eIdentityCertificate = .mockExpired + client.mlsThumbPrint = E2eIdentityCertificate.mockExpired.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, @@ -397,6 +426,7 @@ final class DeviceDetailsViewTests: XCTestCase, CoreDataFixtureTestHelper { func testWhenE2eidentityViewIsEnabledAndCertificateIsNotActivatedInDarkModeForOtherClient() { client.e2eIdentityCertificate = .mockNotActivated + client.mlsThumbPrint = E2eIdentityCertificate.mockNotActivated.mlsThumbprint let viewModel = prepareViewModel( isProteusVerificationEnabled: true, diff --git a/wire-ios/Wire-iOS Tests/OtherUserDeviceDetailsViewTests.swift b/wire-ios/Wire-iOS Tests/OtherUserDeviceDetailsViewTests.swift index 327cab85f53..b5194ff8c38 100644 --- a/wire-ios/Wire-iOS Tests/OtherUserDeviceDetailsViewTests.swift +++ b/wire-ios/Wire-iOS Tests/OtherUserDeviceDetailsViewTests.swift @@ -33,6 +33,9 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { .uppercased() .splitStringIntoLines(charactersPerLine: 16) + private let mockMLSThumbprint: String = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl" + .uppercased() + private var coreDataFixture: CoreDataFixture! private var sut: DeviceInfoViewController! private var client: UserClient! @@ -72,6 +75,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { ) -> DeviceInfoViewModel { let mockSession = UserSessionMock(mockUser: .createSelfUser(name: "Joe")) mockSession.isE2eIdentityEnabled = isE2eIdentityEnabled + client.mlsThumbPrint = mlsThumbprint var certificate: E2eIdentityCertificate = switch status { case .notActivated: .mockNotActivated @@ -129,25 +133,9 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { ) } - func testThatIsShowsDebugMenu_WhenE2eidentityViewIsDisabled() { - let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, - status: .notActivated, - isProteusVerificationEnabled: true, - isE2eIdentityEnabled: false, - proteusKeyFingerPrint: mockFingerPrint, - showDebugMenu: true - ) - snapshotHelper.verify( - matching: setupWrappedInNavigationController( - viewModel: viewModel - ) - ) - } - func testWhenE2eidentityViewIsDisabled() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .notActivated, isProteusVerificationEnabled: true, isE2eIdentityEnabled: false, @@ -162,7 +150,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { func testWhenE2eidentityViewIsEnabledAndCertificateIsValid() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .valid, isProteusVerificationEnabled: true, isE2eIdentityEnabled: true, @@ -175,7 +163,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { func testWhenE2eidentityViewIsEnabledAndCertificateIsValidWhenProteusIsNotVerifiedThenBlueShieldIsNotShown() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .valid, isProteusVerificationEnabled: false, isE2eIdentityEnabled: true, @@ -188,7 +176,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { func testWhenE2eidentityViewIsEnabledAndCertificateIsRevoked() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .revoked, isProteusVerificationEnabled: true, isE2eIdentityEnabled: true, @@ -201,7 +189,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { func testWhenE2eidentityViewIsEnabledAndCertificateIsExpired() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .expired, isProteusVerificationEnabled: true, isE2eIdentityEnabled: true, @@ -215,7 +203,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { func testWhenE2eidentityViewIsEnabledAndCertificateIsNotActivated() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .notActivated, isProteusVerificationEnabled: true, isE2eIdentityEnabled: true, @@ -228,7 +216,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { func testWhenE2eidentityViewIsEnabledAndCertificateIsInvalid() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .invalid, isProteusVerificationEnabled: true, isE2eIdentityEnabled: true, @@ -243,7 +231,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { func testWhenE2eidentityViewIsDisabledInDarkMode() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .notActivated, isProteusVerificationEnabled: true, isE2eIdentityEnabled: false, @@ -256,7 +244,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { func testWhenE2eidentityViewIsEnabledAndCertificateIsValidInDarkMode() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .valid, isProteusVerificationEnabled: true, isE2eIdentityEnabled: true, @@ -269,7 +257,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { func testWhenE2eidentityViewIsEnabledAndCertificateIsRevokedInDarkMode() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .revoked, isProteusVerificationEnabled: true, isE2eIdentityEnabled: true, @@ -282,7 +270,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { func testWhenE2eidentityViewIsEnabledAndCertificateIsExpiredInDarkMode() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .expired, isProteusVerificationEnabled: true, isE2eIdentityEnabled: true, @@ -295,7 +283,7 @@ final class OtherUserDeviceDetailsViewTests: XCTestCase { func testWhenE2eidentityViewIsEnabledAndCertificateIsNotActivatedInDarkMode() { let viewModel = prepareViewModel( - mlsThumbprint: mockFingerPrint, + mlsThumbprint: mockMLSThumbprint, status: .notActivated, isProteusVerificationEnabled: true, isE2eIdentityEnabled: true, diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/AddParticipantsViewControllerSnapshotTests/testThatTabBarIsNotShown_WhenBotCanNotBeAdded.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/AddParticipantsViewControllerSnapshotTests/testThatTabBarIsNotShown_WhenBotCanNotBeAdded.1.png new file mode 100644 index 00000000000..c9cbefde249 Binary files /dev/null and b/wire-ios/Wire-iOS Tests/ReferenceImages/AddParticipantsViewControllerSnapshotTests/testThatTabBarIsNotShown_WhenBotCanNotBeAdded.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/AddParticipantsViewControllerSnapshotTests/testThatTabBarIsShown_WhenBotCanBeAdded.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/AddParticipantsViewControllerSnapshotTests/testThatTabBarIsShown_WhenBotCanBeAdded.1.png index 39836701ced..e146ac579d3 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/AddParticipantsViewControllerSnapshotTests/testThatTabBarIsShown_WhenBotCanBeAdded.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/AddParticipantsViewControllerSnapshotTests/testThatTabBarIsShown_WhenBotCanBeAdded.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForInviteButton.DarkTheme.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForInviteButton.DarkTheme.png deleted file mode 100644 index 20274525461..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForInviteButton.DarkTheme.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForInviteButton.LightTheme.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForInviteButton.LightTheme.png deleted file mode 100644 index 9de2dbffa06..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForInviteButton.LightTheme.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForNoSubtitle.DarkTheme.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForNoSubtitle.DarkTheme.png deleted file mode 100644 index 033e107d6b2..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForNoSubtitle.DarkTheme.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForNoSubtitle.LightTheme.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForNoSubtitle.LightTheme.png deleted file mode 100644 index 36ee43cad22..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForNoSubtitle.LightTheme.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButton.DarkTheme.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButton.DarkTheme.png deleted file mode 100644 index 2a51d67e225..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButton.DarkTheme.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButton.LightTheme.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButton.LightTheme.png deleted file mode 100644 index 983767758a6..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButton.LightTheme.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButtonWithALongUsername.DarkTheme.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButtonWithALongUsername.DarkTheme.png deleted file mode 100644 index f4e2ab6e617..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButtonWithALongUsername.DarkTheme.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButtonWithALongUsername.LightTheme.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButtonWithALongUsername.LightTheme.png deleted file mode 100644 index 3ef94d9f701..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsCellSnapshotTests/testForOpenButtonWithALongUsername.LightTheme.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForContactsAndIndexSectionBarAreShown.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForContactsAndIndexSectionBarAreShown.1.png deleted file mode 100644 index d15319eb778..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForContactsAndIndexSectionBarAreShown.1.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForContactsWithoutSections.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForContactsWithoutSections.1.png deleted file mode 100644 index 6fb9b611394..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForContactsWithoutSections.1.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForNoContacts.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForNoContacts.1.png deleted file mode 100644 index 4e01bdb86c1..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForNoContacts.1.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForNoSearchResult.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForNoSearchResult.1.png deleted file mode 100644 index 85ec4c8af8b..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ContactsViewControllerSnapshotTests/testForNoSearchResult.1.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationCreationControllerSnapshotTests/testTeamGroupOptions_withoutServices.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationCreationControllerSnapshotTests/testTeamGroupOptions_withoutServices.1.png new file mode 100644 index 00000000000..d6b1f89bb96 Binary files /dev/null and b/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationCreationControllerSnapshotTests/testTeamGroupOptions_withoutServices.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByFavourites.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByFavourites.1.png index 7f82b993419..4312353970c 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByFavourites.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByFavourites.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByGroups.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByGroups.1.png index 1367ddfdca2..de7c3056403 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByGroups.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByGroups.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByOneOnOne.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByOneOnOne.1.png index 32315118305..202fbe58c0e 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByOneOnOne.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/ConversationListViewControllerSnapshotTests/testForShowingConversationsFilteredByOneOnOne.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/DeviceDetailsViewTests/testWhenE2eidentityIsDisabledAndMLSIsEnabled.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/DeviceDetailsViewTests/testWhenE2eidentityIsDisabledAndMLSIsEnabled.1.png new file mode 100644 index 00000000000..90f866d70ef Binary files /dev/null and b/wire-ios/Wire-iOS Tests/ReferenceImages/DeviceDetailsViewTests/testWhenE2eidentityIsDisabledAndMLSIsEnabled.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOfflineState.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOfflineState.1.png index 08e28dced5f..0b89598fab2 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOfflineState.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOfflineState.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOnlineState.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOnlineState.1.png index 979b731143b..8f66cad8db7 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOnlineState.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOnlineState.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOnlineSynchronizing.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOnlineSynchronizing.1.png index 2380a6beb80..2f2d487994a 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOnlineSynchronizing.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/NetworkStatusViewControllerSnapshotTests/testOnlineSynchronizing.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/OtherUserDeviceDetailsViewTests/testThatIsShowsDebugMenu_WhenE2eidentityViewIsDisabled.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/OtherUserDeviceDetailsViewTests/testThatIsShowsDebugMenu_WhenE2eidentityViewIsDisabled.1.png deleted file mode 100644 index 18dcfb55b40..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/OtherUserDeviceDetailsViewTests/testThatIsShowsDebugMenu_WhenE2eidentityViewIsDisabled.1.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/OtherUserDeviceDetailsViewTests/testWhenE2eidentityViewIsDisabled.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/OtherUserDeviceDetailsViewTests/testWhenE2eidentityViewIsDisabled.1.png index 18dcfb55b40..27b9fc97bd3 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/OtherUserDeviceDetailsViewTests/testWhenE2eidentityViewIsDisabled.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/OtherUserDeviceDetailsViewTests/testWhenE2eidentityViewIsDisabled.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/OtherUserDeviceDetailsViewTests/testWhenE2eidentityViewIsDisabledInDarkMode.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/OtherUserDeviceDetailsViewTests/testWhenE2eidentityViewIsDisabledInDarkMode.1.png index 6dbc5cb4b74..75db57d082d 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/OtherUserDeviceDetailsViewTests/testWhenE2eidentityViewIsDisabledInDarkMode.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/OtherUserDeviceDetailsViewTests/testWhenE2eidentityViewIsDisabledInDarkMode.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitState.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitState.1.png index bec940e0fba..ca3583c8a89 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitState.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitState.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitStateInDarkTheme.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitStateInDarkTheme.1.png index 1473ccfc386..eb59ef032cd 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitStateInDarkTheme.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitStateInDarkTheme.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitStateInDarkTheme_ifForcedApplock.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitStateInDarkTheme_ifForcedApplock.1.png index 2262af1cadf..7ff75e34fb0 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitStateInDarkTheme_ifForcedApplock.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitStateInDarkTheme_ifForcedApplock.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitState_ifForcedApplock.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitState_ifForcedApplock.1.png index fcd6bf11d6e..afd1bb41c81 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitState_ifForcedApplock.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForInitState_ifForcedApplock.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForPasscodePassed.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForPasscodePassed.1.png index 61b10d5de10..0b6253a1ba8 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForPasscodePassed.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/PasscodeSetupViewControllerTests/testForPasscodePassed.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ShareContactsViewControllerSnapshotTests/testForContactsPermissionDenied.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ShareContactsViewControllerSnapshotTests/testForContactsPermissionDenied.1.png deleted file mode 100644 index f05d66b7e46..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ShareContactsViewControllerSnapshotTests/testForContactsPermissionDenied.1.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/ShareContactsViewControllerSnapshotTests/testForInitState.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/ShareContactsViewControllerSnapshotTests/testForInitState.1.png deleted file mode 100644 index bddf498ff01..00000000000 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/ShareContactsViewControllerSnapshotTests/testForInitState.1.png and /dev/null differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContact.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContact.1.png index 9c5b06320cf..a45efff9335 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContact.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContact.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContactWhenSelfIsPartner.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContactWhenSelfIsPartner.1.png index 9c5b06320cf..a45efff9335 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContactWhenSelfIsPartner.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContactWhenSelfIsPartner.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContactWhenSelfIsTeamMember.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContactWhenSelfIsTeamMember.1.png index 9c5b06320cf..a45efff9335 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContactWhenSelfIsTeamMember.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerNoContactWhenSelfIsTeamMember.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerWrappedInNavigationController.1.png b/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerWrappedInNavigationController.1.png index 9c5b06320cf..a45efff9335 100644 Binary files a/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerWrappedInNavigationController.1.png and b/wire-ios/Wire-iOS Tests/ReferenceImages/StartUIViewControllerSnapshotTests/testStartUIViewControllerWrappedInNavigationController.1.png differ diff --git a/wire-ios/Wire-iOS Tests/ShareContactsViewControllerSnapshotTests.swift b/wire-ios/Wire-iOS Tests/ShareContactsViewControllerSnapshotTests.swift deleted file mode 100644 index 71793aa0f74..00000000000 --- a/wire-ios/Wire-iOS Tests/ShareContactsViewControllerSnapshotTests.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import WireTestingPackage -import XCTest - -@testable import Wire - -final class ShareContactsViewControllerSnapshotTests: XCTestCase { - - private var sut: ShareContactsViewController! - private var snapshotHelper: SnapshotHelper! - - override func setUp() { - UIColor.setAccentOverride(.blue) - snapshotHelper = SnapshotHelper() - sut = ShareContactsViewController() - } - - override func tearDown() { - snapshotHelper = nil - sut = nil - UIColor.setAccentOverride(nil) - } - - func testForInitState() { - snapshotHelper.verify(matching: sut) - } - - func testForContactsPermissionDenied() { - sut.displayContactsAccessDeniedMessage(animated: false) - snapshotHelper.verify(matching: sut) - } -} diff --git a/wire-ios/Wire-iOS Tests/StartUIViewControllerSnapshotTests.swift b/wire-ios/Wire-iOS Tests/StartUIViewControllerSnapshotTests.swift index d7918194afa..1626288f27d 100644 --- a/wire-ios/Wire-iOS Tests/StartUIViewControllerSnapshotTests.swift +++ b/wire-ios/Wire-iOS Tests/StartUIViewControllerSnapshotTests.swift @@ -22,30 +22,6 @@ import XCTest @testable import Wire -final class MockAddressBookHelper: NSObject, AddressBookHelperProtocol { - - var isAddressBookAccessDisabled: Bool = false - - var accessStatusDidChangeToGranted: Bool = true - - static var sharedHelper: AddressBookHelperProtocol = MockAddressBookHelper() - - func persistCurrentAccessStatus() {} - - var isAddressBookAccessGranted: Bool { - false - } - - var isAddressBookAccessUnknown: Bool { - true - } - - func requestPermissions(_ callback: ((Bool) -> Void)?) { - // no-op - callback?(false) - } -} - final class StartUIViewControllerSnapshotTests: CoreDataSnapshotTestCase { // MARK: - Properties @@ -53,7 +29,6 @@ final class StartUIViewControllerSnapshotTests: CoreDataSnapshotTestCase { private var snapshotHelper: SnapshotHelper! private var mockMainCoordinator: AnyMainCoordinator! private var sut: StartUIViewController! - private var mockAddressBookHelper: MockAddressBookHelper! private var userSession: UserSessionMock! // MARK: - setUp @@ -67,9 +42,9 @@ final class StartUIViewControllerSnapshotTests: CoreDataSnapshotTestCase { override func setUp() { super.setUp() snapshotHelper = SnapshotHelper() - mockAddressBookHelper = MockAddressBookHelper() SelfUser.provider = selfUserProvider userSession = UserSessionMock() + accentColor = .blue } // MARK: - tearDown @@ -77,7 +52,6 @@ final class StartUIViewControllerSnapshotTests: CoreDataSnapshotTestCase { override func tearDown() { snapshotHelper = nil sut = nil - mockAddressBookHelper = nil SelfUser.provider = nil userSession = nil mockMainCoordinator = nil @@ -89,7 +63,6 @@ final class StartUIViewControllerSnapshotTests: CoreDataSnapshotTestCase { func setupSut() { sut = StartUIViewController( - addressBookHelperType: MockAddressBookHelper.self, userSession: userSession, mainCoordinator: mockMainCoordinator, createGroupConversationUIBuilder: MockCreateGroupConversationViewControllerBuilderProtocol(), diff --git a/wire-ios/Wire-iOS Tests/UserInterface/MainController/ConversationFilterSelectorTests.swift b/wire-ios/Wire-iOS Tests/UserInterface/MainController/ConversationFilterSelectorTests.swift new file mode 100644 index 00000000000..c573b9afe12 --- /dev/null +++ b/wire-ios/Wire-iOS Tests/UserInterface/MainController/ConversationFilterSelectorTests.swift @@ -0,0 +1,105 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel +import XCTest + +@testable import Wire + +final class ConversationFilterSelectorTests: XCTestCase { + + private var sut: ConversationFilterSelector! + private var conversationDirectory: MockConversationDirectory! + private var conversationFilter: ConversationFilter? + private var conversationFilterDidChange: Bool! + + @MainActor + override func setUpWithError() throws { + conversationDirectory = MockConversationDirectory() + sut = ConversationFilterSelector( + conversationFilter: { [unowned self] in conversationFilter }, + updateConversationFilter: { [unowned self] newValue in + conversationFilter = newValue + conversationFilterDidChange = true + } + ) + conversationFilterDidChange = false + } + + @MainActor + override func tearDownWithError() throws { + conversationDirectory = nil + sut = nil + conversationFilterDidChange = nil + } + + @MainActor + func testConversationDirectoryDidChange_whenFolderFilterSelected() throws { + // GIVEN + let folderA = MockLabel(remoteIdentifier: UUID()) + let folderB = MockLabel(remoteIdentifier: UUID()) + + conversationFilter = .folder(id: folderA.remoteIdentifier!, name: "folderName") + + // WHEN + conversationDirectory.nonDeletedFolders = [folderA, folderB] + sut.conversationDirectoryDidChange(conversationDirectory: conversationDirectory, changeInfo: .someValue) + + // THEN + XCTAssertFalse(conversationFilterDidChange) + XCTAssertEqual(conversationFilter, .folder(id: folderA.remoteIdentifier!, name: "folderName")) + + // WHEN + conversationDirectory.nonDeletedFolders = [folderB] + sut.conversationDirectoryDidChange(conversationDirectory: conversationDirectory, changeInfo: .someValue) + + // THEN + XCTAssertTrue(conversationFilterDidChange) + XCTAssertNil(conversationFilter) + } + + @MainActor + func testConversationDirectoryDidChange_whenNotFolderFilterSelected() throws { + let testCases: [ConversationFilter?] = [ + .favorites, + .groups, + .oneOnOne, + .none + ] + + for testCase in testCases { + // GIVEN + conversationFilter = testCase + + // WHEN + sut.conversationDirectoryDidChange(conversationDirectory: conversationDirectory, changeInfo: .someValue) + + // THEN + XCTAssertFalse(conversationFilterDidChange) + } + } + +} + +private extension ConversationDirectoryChangeInfo { + static let someValue = ConversationDirectoryChangeInfo( + reloaded: false, + updatedLists: [], + updatedFolders: false + ) +} diff --git a/wire-ios/Wire-iOS Tests/UserInterface/MainCoordinator/MockMainCoordinator.swift b/wire-ios/Wire-iOS Tests/UserInterface/MainCoordinator/MockMainCoordinator.swift index 72f69ab3573..218f65f79a5 100644 --- a/wire-ios/Wire-iOS Tests/UserInterface/MainCoordinator/MockMainCoordinator.swift +++ b/wire-ios/Wire-iOS Tests/UserInterface/MainCoordinator/MockMainCoordinator.swift @@ -32,6 +32,11 @@ final class MockMainCoordinator: MainCoordinatorProtocol { fatalError("Mock method not implemented") } + @MainActor + func applyConversationFilter(_ filter: ConversationFilter?) { + fatalError("Mock method not implemented") + } + @MainActor func showArchive() { fatalError("Mock method not implemented") diff --git a/wire-ios/Wire-iOS Tests/Utils/CoreDataSnapshotTestCase.swift b/wire-ios/Wire-iOS Tests/Utils/CoreDataSnapshotTestCase.swift index 97c4319b27e..a7f79334cd2 100644 --- a/wire-ios/Wire-iOS Tests/Utils/CoreDataSnapshotTestCase.swift +++ b/wire-ios/Wire-iOS Tests/Utils/CoreDataSnapshotTestCase.swift @@ -37,7 +37,7 @@ class CoreDataSnapshotTestCase: ZMSnapshotTestCase { // var selfUserProvider: SelfUserProvider! - open override func setUp() { + override func setUp() { super.setUp() snapshotBackgroundColor = .white setupTestObjects() @@ -46,7 +46,7 @@ class CoreDataSnapshotTestCase: ZMSnapshotTestCase { selfUserProvider = SelfProvider(providedSelfUser: selfUser) } - open override func tearDown() { + override func tearDown() { selfUser = nil otherUser = nil otherUserConversation = nil diff --git a/wire-ios/Wire-iOS Tests/Utils/ZMSnapshotTestCase.swift b/wire-ios/Wire-iOS Tests/Utils/ZMSnapshotTestCase.swift index cdb82eb3c33..caf94180356 100644 --- a/wire-ios/Wire-iOS Tests/Utils/ZMSnapshotTestCase.swift +++ b/wire-ios/Wire-iOS Tests/Utils/ZMSnapshotTestCase.swift @@ -37,7 +37,7 @@ class ZMSnapshotTestCase: XCTestCase { var documentsDirectory: URL? - open override func setUp() { + override func setUp() { super.setUp() XCTAssertEqual(UIScreen.main.scale, 3, "Snapshot tests need to be run on a device with a 3x scale") @@ -83,7 +83,7 @@ class ZMSnapshotTestCase: XCTestCase { uiMOC = coreDataStack.viewContext } - open override func tearDown() { + override func tearDown() { if needsCaches { wipeCaches() } diff --git a/wire-ios/Wire-iOS UnitTests/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModelTests.swift b/wire-ios/Wire-iOS UnitTests/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModelTests.swift index 0bd19f52792..dbeeaf370d2 100644 --- a/wire-ios/Wire-iOS UnitTests/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModelTests.swift +++ b/wire-ios/Wire-iOS UnitTests/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModelTests.swift @@ -27,7 +27,7 @@ final class DeveloperDebugActionsViewModelTests: XCTestCase { // when // then - XCTAssertEqual(viewModel.buttons.count, 6) + XCTAssertEqual(viewModel.buttons.count, 8) } // MARK: - Helpers diff --git a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj index 7b311a7c585..70828a7747a 100644 --- a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj +++ b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj @@ -39,7 +39,7 @@ 01BC68512CE3AF9E00445243 /* EmptyConversationSearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC68502CE3AF9E00445243 /* EmptyConversationSearchResultsView.swift */; }; 01BC68652CE496A500445243 /* EmptyPlaceholderContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC68642CE496A500445243 /* EmptyPlaceholderContainerView.swift */; }; 01C1A7C72A54C45A0058D578 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 01C1A7C62A54C45A0058D578 /* SnapshotTesting */; }; - 01C488B02C8A45F80066789E /* WireAnalytics_DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C488AF2C8A45F80066789E /* WireAnalytics_DatadogTests.swift */; }; + 01D2C78F2CECC41D00F05E5E /* QRCodeScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D2C78E2CECC41D00F05E5E /* QRCodeScannerViewController.swift */; }; 01D8E71A2BA39CE900B71CB7 /* PrivacyWarningChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D8E7192BA39CE900B71CB7 /* PrivacyWarningChecker.swift */; }; 01F5EAE42B712DD7009FD25D /* LogFileDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F5EAE32B712DD6009FD25D /* LogFileDestination.swift */; }; 06024ABC29E69A860032CC31 /* ConversationMessageFailedRecipientsCellDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06024ABB29E69A850032CC31 /* ConversationMessageFailedRecipientsCellDescription.swift */; }; @@ -257,7 +257,6 @@ 1E2268041B395722003980E3 /* people-01.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E2268031B395722003980E3 /* people-01.json */; }; 1E2268061B3957A7003980E3 /* conversations-01.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E2268051B3957A7003980E3 /* conversations-01.json */; }; 1E8772281B0C8CAB005BDDC6 /* emo-test-03.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E8772271B0C8CAB005BDDC6 /* emo-test-03.json */; }; - 1E98C81B1BD1719500C7A7A2 /* ContactsDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E98C81A1BD1719500C7A7A2 /* ContactsDataSourceTests.swift */; }; 1EFB23DC1ACEDE5400244571 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 1EFB23DE1ACEDE5400244571 /* Localizable.stringsdict */; }; 252183202B4C00DF000C4325 /* SaveFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2521831F2B4C00DF000C4325 /* SaveFileManager.swift */; }; 252183222B4C02B9000C4325 /* MockSaveFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 252183212B4C02B9000C4325 /* MockSaveFileManager.swift */; }; @@ -310,7 +309,6 @@ 549D55AC1DBE03990035EB66 /* Settings+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549D55AB1DBE03990035EB66 /* Settings+Logging.swift */; }; 54B27986258113FD0043ED25 /* AuthenticatedRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B27985258113FD0043ED25 /* AuthenticatedRouter.swift */; }; 54D355E025559BFC0004DAC7 /* ActiveCallRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D355DF25559BFC0004DAC7 /* ActiveCallRouter.swift */; }; - 54F7C27F1D74367E004D8087 /* AddressBookHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F7C27E1D74367E004D8087 /* AddressBookHelper.swift */; }; 54FF9E281E813BA500323D2E /* DebugAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54FF9E271E813BA500323D2E /* DebugAlert.swift */; }; 5501FFAB22B28F330050D8FC /* MockSelfLegalHoldSubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5501FFA922B28D210050D8FC /* MockSelfLegalHoldSubject.swift */; }; 5501FFC922B398B30050D8FC /* UserInputRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5501FFC822B398B20050D8FC /* UserInputRequest.swift */; }; @@ -324,10 +322,8 @@ 5902F8E32BF7B18B00F1D392 /* ArchivedListViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5902F8DF2BF7B14F00F1D392 /* ArchivedListViewControllerSnapshotTests.swift */; }; 59076F952C934A9800AE7529 /* WireAccountImageUI in Frameworks */ = {isa = PBXBuildFile; productRef = 59076F942C934A9800AE7529 /* WireAccountImageUI */; }; 59076F972C934AA100AE7529 /* WireAccountImageUI in Frameworks */ = {isa = PBXBuildFile; productRef = 59076F962C934AA100AE7529 /* WireAccountImageUI */; }; - 590A5F072BF4CB1B008E87D8 /* PermissionDeniedViewController+addressBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590A5F062BF4CB1B008E87D8 /* PermissionDeniedViewController+addressBook.swift */; }; 590A5F092BF4CB6B008E87D8 /* PermissionDeniedViewController+notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590A5F082BF4CB6B008E87D8 /* PermissionDeniedViewController+notifications.swift */; }; 590A5F0B2BF4CBCE008E87D8 /* PermissionDeniedViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590A5F0A2BF4CBCE008E87D8 /* PermissionDeniedViewControllerDelegate.swift */; }; - 590BD7B82C00BD1C0093435A /* ShareContactsViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590BD7B72C00BD1C0093435A /* ShareContactsViewControllerDelegate.swift */; }; 590DCA082C971A56002D0A2C /* WireSidebarUI in Frameworks */ = {isa = PBXBuildFile; productRef = 590DCA072C971A56002D0A2C /* WireSidebarUI */; }; 590DCA0A2C971AFF002D0A2C /* WireFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 590DCA092C971AFF002D0A2C /* WireFoundation */; }; 5915B94B2BF4A70900215817 /* ShouldPresentNotificationPermissionHintUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5915B94A2BF4A70900215817 /* ShouldPresentNotificationPermissionHintUseCaseProtocol.swift */; }; @@ -336,6 +332,7 @@ 5915B9582BF4BA9700215817 /* DidPresentNotificationPermissionHintUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5915B9572BF4BA9700215817 /* DidPresentNotificationPermissionHintUseCaseProtocol.swift */; }; 5915B95A2BF4BAFF00215817 /* DidPresentNotificationPermissionHintUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5915B9592BF4BAFE00215817 /* DidPresentNotificationPermissionHintUseCase.swift */; }; 5915B95C2BF4BB5300215817 /* NativelySupportedUserDefaultsKey+lastTimeNotificationPermissionHintWasShown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5915B95B2BF4BB5300215817 /* NativelySupportedUserDefaultsKey+lastTimeNotificationPermissionHintWasShown.swift */; }; + 59191A652D0051C7001AB388 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59191A642D0051C7001AB388 /* WireLogging */; }; 591B6E172C8B095B009F8A7B /* WireNotificationEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9A8585298B0A3B00064A9C /* WireNotificationEngine.framework */; }; 591B6E182C8B095B009F8A7B /* WireNotificationEngine.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EE9A8585298B0A3B00064A9C /* WireNotificationEngine.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 591B6E192C8B0960009F8A7B /* WireCommonComponents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F1FEA14A21DCEB1700790A54 /* WireCommonComponents.framework */; }; @@ -358,12 +355,18 @@ 594211CD2C90591700576053 /* PDFFilePreviewGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594211CC2C90591700576053 /* PDFFilePreviewGenerator.swift */; }; 594211D02C90700200576053 /* FileMetaDataGeneratorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594211CF2C90700200576053 /* FileMetaDataGeneratorProtocol.swift */; }; 594211D32C90778E00576053 /* SettingsDebugReportViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594211D12C90775700576053 /* SettingsDebugReportViewModelProtocol.swift */; }; + 594341D72D08611700A6C0B5 /* WireCountly in Frameworks */ = {isa = PBXBuildFile; productRef = 594341D62D08611700A6C0B5 /* WireCountly */; }; 5945D02A2C219F7200D039E3 /* WireDesign in Frameworks */ = {isa = PBXBuildFile; productRef = 5945D0292C219F7200D039E3 /* WireDesign */; }; + 594906A52D0713C000238104 /* WireAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 594906A42D0713C000238104 /* WireAnalytics */; }; 594C4E2B2CCA98F000F13D03 /* AccountImageSourceMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594C4E2A2CCA98E800F13D03 /* AccountImageSourceMapping.swift */; }; 5950467D2C6F3DA9005315DE /* NetworkStatusViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5950467C2C6F3DA9005315DE /* NetworkStatusViewSnapshotTests.swift */; }; 5950A9472C2F1C31005AB9CE /* AvailabilityMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5950A9462C2F1C31005AB9CE /* AvailabilityMappings.swift */; }; 59510DD32CB3E6EE00BCD5FD /* SidebarViewControllerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59510DD22CB3E6E700BCD5FD /* SidebarViewControllerBuilder.swift */; }; 5952693F2BE8C93B001C1E8B /* AppLockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5952693E2BE8C93B001C1E8B /* AppLockView.swift */; }; + 59537D8F2CFFA05600920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59537D8E2CFFA05600920B59 /* WireLogging */; }; + 59537D912CFFA0BA00920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59537D902CFFA0BA00920B59 /* WireLogging */; }; + 59537D932CFFA0DA00920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59537D922CFFA0DA00920B59 /* WireLogging */; }; + 59537D952CFFA11A00920B59 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59537D942CFFA11A00920B59 /* WireLogging */; }; 595BFD7D2CA9365800D02361 /* ConversationListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 595BFD7C2CA9365200D02361 /* ConversationListCoordinator.swift */; }; 595C49362CA93D7200F8F881 /* AnyConversationListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 595C49352CA93D6D00F8F881 /* AnyConversationListCoordinator.swift */; }; 595C49692CA995E900F8F881 /* WireSettingsUI in Frameworks */ = {isa = PBXBuildFile; productRef = 595C49682CA995E900F8F881 /* WireSettingsUI */; }; @@ -694,6 +697,7 @@ 6636CFDE2C519980001403F9 /* EditingStateControllable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6636CFDD2C519980001403F9 /* EditingStateControllable.swift */; }; 669105C327397E6A00324115 /* NSAttributedString+DownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669105C227397E6900324115 /* NSAttributedString+DownTests.swift */; }; 70355A7127AAD96E00F02C76 /* SecurityLevelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70355A7027AAD96E00F02C76 /* SecurityLevelView.swift */; }; + 76D15E712CEFAE8A0059215D /* WireIndividualToTeamMigrationUI in Frameworks */ = {isa = PBXBuildFile; productRef = 76D15E702CEFAE8A0059215D /* WireIndividualToTeamMigrationUI */; }; 7A702FB7286322D800004580 /* ConversationEncryptionProtocolCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A702FB6286322D800004580 /* ConversationEncryptionProtocolCell.swift */; }; 7C0BB6E61FE682A200386A19 /* AccountSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0BB6E41FE680F200386A19 /* AccountSelectionViewController.swift */; }; 7C23A26D20247500005FEB54 /* ShareViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C23A26B20247474005FEB54 /* ShareViewControllerTests.swift */; }; @@ -749,7 +753,6 @@ 8711A5A01E0C320100043ACF /* FloatingPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8711A59F1E0C320100043ACF /* FloatingPoint.swift */; }; 8711A5A21E0D395A00043ACF /* CollectionCellHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8711A5A11E0D395A00043ACF /* CollectionCellHeader.swift */; }; 8711A5A41E0D6C8800043ACF /* TwoLineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8711A5A31E0D6C8800043ACF /* TwoLineTitleView.swift */; }; - 8712134B1FA37882008B6767 /* TrackingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8712134A1FA37881008B6767 /* TrackingManager.swift */; }; 8719FD671C88802C005FBEC0 /* Delay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8719FD661C88802C005FBEC0 /* Delay.swift */; }; 871BC0281D34F5D200DF0793 /* EmoticonSubstitutionConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 871BC0251D34F5D200DF0793 /* EmoticonSubstitutionConfiguration.swift */; }; 871BC22D1D34F8F800DF0793 /* RotationAwareNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 871BC1441D34F8F800DF0793 /* RotationAwareNavigationController.swift */; }; @@ -880,7 +883,6 @@ 87D5E4421C3E79C600EB8289 /* Data+Fingerprint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87D5E4411C3E79C600EB8289 /* Data+Fingerprint.swift */; }; 87D679E61EBC8002005BEB4C /* String+Compatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87D679E51EBC8002005BEB4C /* String+Compatibility.swift */; }; 87D7E1C520CACA820064C03D /* CallViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87D7E1C420CACA820064C03D /* CallViewControllerTests.swift */; }; - 87DCF4941D34E9BA00BB420F /* ZMAddressBookContact+LocalInvitation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87DCF48C1D34E9BA00BB420F /* ZMAddressBookContact+LocalInvitation.swift */; }; 87DCF49B1D34E9E600BB420F /* LocationSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87DCF4961D34E9E600BB420F /* LocationSelectionViewController.swift */; }; 87DCF49C1D34E9E600BB420F /* LocationSendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87DCF4971D34E9E600BB420F /* LocationSendViewController.swift */; }; 87DCF49D1D34E9E600BB420F /* MapKit+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87DCF4981D34E9E600BB420F /* MapKit+Helper.swift */; }; @@ -976,7 +978,6 @@ A9712D5E2743C4FA0008F380 /* GiphyConfirmationViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9712D5C2743C4640008F380 /* GiphyConfirmationViewControllerSnapshotTests.swift */; }; A97192B3221302DD00E72AD1 /* MockUserRight.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97192B2221302DD00E72AD1 /* MockUserRight.swift */; }; A972840A2384247D00AFEFC6 /* BackupExcluderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97284072384244E00AFEFC6 /* BackupExcluderTests.swift */; }; - A9730DDA250274150061BF36 /* CountlyInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9730DD9250274150061BF36 /* CountlyInstance.swift */; }; A9730DDC250287360061BF36 /* Bundle+ThirdPartyKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9730DDB250287360061BF36 /* Bundle+ThirdPartyKeys.swift */; }; A97559F42729AA7100217714 /* TextSearchResultsViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97559F32729AA7100217714 /* TextSearchResultsViewSnapshotTests.swift */; }; A978D993236C587B00B568AE /* ZMUserSession+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = A978D992236C587B00B568AE /* ZMUserSession+Shared.swift */; }; @@ -1032,7 +1033,6 @@ A9E674D524F508B70058FC72 /* WireEmail.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E674D424F508B70058FC72 /* WireEmail.swift */; }; A9E674D724F514710058FC72 /* email.json in Resources */ = {isa = PBXBuildFile; fileRef = A9E674D624F514700058FC72 /* email.json */; }; A9E674DB24F51A5C0058FC72 /* WireEmailTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E674D924F51A190058FC72 /* WireEmailTests.swift */; }; - A9EB722323C1581A00DA8240 /* ContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB722223C1581A00DA8240 /* ContactsViewController.swift */; }; A9EB722523C3A47B00DA8240 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB722423C3A47B00DA8240 /* AppDelegate.swift */; }; A9EB722823C3A9F600DA8240 /* ConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB722723C3A9F600DA8240 /* ConversationViewController.swift */; }; A9F57E3D2419A83100EEA912 /* TokenContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F57E3C2419A83100EEA912 /* TokenContainer.swift */; }; @@ -1164,6 +1164,7 @@ CB43DBC42CE639E800BF5AEB /* FolderPickerViewControllerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB43DBC32CE639E800BF5AEB /* FolderPickerViewControllerBuilder.swift */; }; CB4870F22C7F4FE5001E9151 /* WireTransportSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB4870F12C7F4FE5001E9151 /* WireTransportSupport.framework */; }; CB4E15122C81CC81005DDEC8 /* Down in Frameworks */ = {isa = PBXBuildFile; productRef = CB4E15112C81CC81005DDEC8 /* Down */; }; + CB8E51E32CF89D9B00F7F01D /* ConversationFilterSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB8E51E22CF89D9B00F7F01D /* ConversationFilterSelector.swift */; }; D30880FB292CD8F200DDEAB0 /* CallingBottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D30880FA292CD8F200DDEAB0 /* CallingBottomSheetViewController.swift */; }; D30880FE292E521D00DDEAB0 /* CallingActionsInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D30880FD292E521D00DDEAB0 /* CallingActionsInfoViewController.swift */; }; D3095761283CEB1C00CEC620 /* MediaShareRestrictionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D309575D283CDFED00CEC620 /* MediaShareRestrictionManager.swift */; }; @@ -1353,7 +1354,6 @@ E91DCC34278DAEDA00B66DD7 /* GroupDetailsServicesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91DCC33278DAEDA00B66DD7 /* GroupDetailsServicesCell.swift */; }; E91DCC36278DE2AE00B66DD7 /* ConversationCreateServicesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91DCC35278DE2AE00B66DD7 /* ConversationCreateServicesCell.swift */; }; E92A07A02817E553006BA73B /* DynamicFontButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E92A079F2817E553006BA73B /* DynamicFontButton.swift */; }; - E92C8CC62C942A5C00E0F7F7 /* Inject in Frameworks */ = {isa = PBXBuildFile; productRef = E92C8CC52C942A5C00E0F7F7 /* Inject */; }; E92D1B95285B773B00366AFC /* LabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E92D1B94285B773B00366AFC /* LabelStyle.swift */; }; E92D31752B0CBAD400F5AAD4 /* FontBookSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E92D31742B0CBAD400F5AAD4 /* FontBookSnapshotTests.swift */; }; E9398D312B5EBF5500C4AFBA /* MLSMigrationSupportCellDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9398D302B5EBF5500C4AFBA /* MLSMigrationSupportCellDescription.swift */; }; @@ -1394,7 +1394,6 @@ E98B61112B5820BD0030E021 /* SwiftMockConversation+ConversationCreation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98B610F2B5820BD0030E021 /* SwiftMockConversation+ConversationCreation.swift */; }; E9929EF72BCEC6FB00C40274 /* AppLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9929EF62BCEC6FB00C40274 /* AppLocationManager.swift */; }; E9929EF92BCEDE5900C40274 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9929EF82BCEDE5900C40274 /* MapViewController.swift */; }; - E996C35C2CA2FA05008924A4 /* TrackingManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E996C35B2CA2FA05008924A4 /* TrackingManagerError.swift */; }; E9A60C7329716A70001D0E14 /* UITextField+DoneBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A60C7229716A70001D0E14 /* UITextField+DoneBarButtonItem.swift */; }; E9ACC8362C10884F0002E608 /* StartUIIconCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9ACC8352C10884F0002E608 /* StartUIIconCell.swift */; }; E9ACC8382C1089D10002E608 /* OpenServicesAdminCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9ACC8372C1089D10002E608 /* OpenServicesAdminCell.swift */; }; @@ -1429,7 +1428,6 @@ E9D6C37D289A9DC100861E4C /* StylableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D6C37C289A9DC100861E4C /* StylableButton.swift */; }; E9D70C892BFDDDDB00D77C2C /* BasicReactionPickerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D70C882BFDDDDB00D77C2C /* BasicReactionPickerSnapshotTests.swift */; }; E9DA4685286C547A00F47C14 /* UIWindow+Orientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DA4684286C547A00F47C14 /* UIWindow+Orientation.swift */; }; - E9DA6FF32C5CCBC600BF8298 /* TrackingManager+UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DA6FF22C5CCBC600BF8298 /* TrackingManager+UIAlertController.swift */; }; E9DD582B2C53929400645A2F /* ChangeEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DD582A2C53929400645A2F /* ChangeEmailViewModel.swift */; }; E9DD582D2C539B3C00645A2F /* ChangeEmailViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DD582C2C539B3C00645A2F /* ChangeEmailViewModelTests.swift */; }; E9DD582F2C539D0C00645A2F /* ChangeEmailError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DD582E2C539D0C00645A2F /* ChangeEmailError.swift */; }; @@ -1512,7 +1510,6 @@ EE88E00623FAC9EA00D24703 /* SelfUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE88E00523FAC9EA00D24703 /* SelfUser.swift */; }; EE8E9267202496C2000F4752 /* NSAttributedString+Down.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8E9266202496C2000F4752 /* NSAttributedString+Down.swift */; }; EE8E926E2024F085000F4752 /* MarkdownTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE8E926D2024F085000F4752 /* MarkdownTextView.swift */; }; - EE948F1923E17778000A663E /* ContactsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE948F1823E17778000A663E /* ContactsDataSource.swift */; }; EE94CA1925C2E490005F1155 /* ScreenCurtainWindowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE94CA0B25C2E48B005F1155 /* ScreenCurtainWindowTests.swift */; }; EE961C202A9F036A008127A6 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE961C1F2A9F036A008127A6 /* Emoji.swift */; }; EE98A09E296ED46D0055E981 /* DeviceConfigurationEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE98A09D296ED46D0055E981 /* DeviceConfigurationEventHandler.swift */; }; @@ -1542,7 +1539,6 @@ EECA90AC28744A060024B301 /* DeveloperFlagsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECA90AB28744A060024B301 /* DeveloperFlagsViewModel.swift */; }; EECDB2562029F5AB00D0D268 /* MarkdownTextStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECDB2552029F5AB00D0D268 /* MarkdownTextStorage.swift */; }; EED92353278DA9D2002D4D9D /* AccentColorPickerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EED92352278DA9D2002D4D9D /* AccentColorPickerSnapshotTests.swift */; }; - EEDEA26823ED609C000EF7E3 /* ContactsViewController+Invite.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDEA26723ED609C000EF7E3 /* ContactsViewController+Invite.swift */; }; EEE104052126F68A003C779B /* CameraController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE104042126F68A003C779B /* CameraController.swift */; }; EEE25B2829719A730008B894 /* Clibsodium.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE25B2629719A730008B894 /* Clibsodium.xcframework */; }; EEE25B2A29719A730008B894 /* cryptobox.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEE25B2729719A730008B894 /* cryptobox.xcframework */; }; @@ -1588,7 +1584,6 @@ EEF71F6521B19F5300F0FB30 /* ConversationCreateReceiptsSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF71F6421B19F5300F0FB30 /* ConversationCreateReceiptsSectionController.swift */; }; EEFF3E7C21B6D1FB000834A6 /* ConversationCreateSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEFF3E7B21B6D1FB000834A6 /* ConversationCreateSectionController.swift */; }; EF00FD741FDA97BA0059FEDC /* UIViewController+Orientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF00FD731FDA97BA0059FEDC /* UIViewController+Orientation.swift */; }; - EF0154B7225B922200A77FB6 /* ContactsViewController+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF0154B6225B922200A77FB6 /* ContactsViewController+Constraints.swift */; }; EF01D28022B136A9009754B2 /* Data+FingerprintStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF01D27F22B136A9009754B2 /* Data+FingerprintStringTests.swift */; }; EF05948422DF07240079B8E1 /* XCTestCase+AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05948322DF07240079B8E1 /* XCTestCase+AccentColor.swift */; }; EF05948622DF18290079B8E1 /* TeamAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF05948522DF18290079B8E1 /* TeamAccountView.swift */; }; @@ -1663,15 +1658,12 @@ EF282B94228AFA970042D6C5 /* RequestPasswordViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF282B93228AFA970042D6C5 /* RequestPasswordViewControllerSnapshotTests.swift */; }; EF2971582099AD8700EFE6A4 /* SelfProfileViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF2971552099AD3000EFE6A4 /* SelfProfileViewControllerTests.swift */; }; EF298BD1228D942A0078BE68 /* ImageManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF298BD0228D942A0078BE68 /* ImageManagerProtocol.swift */; }; - EF298BD3228D9BB20078BE68 /* StartUIViewController+AddressBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF298BD2228D9BB20078BE68 /* StartUIViewController+AddressBook.swift */; }; EF29CDC222D3907000EB2380 /* ConversationListViewControllerViewModel+ConversationListContentDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF29CDC122D3907000EB2380 /* ConversationListViewControllerViewModel+ConversationListContentDelegate.swift */; }; EF29E89C212302CD0023B80C /* AudioMessageViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF29E89B212302CD0023B80C /* AudioMessageViewTests.swift */; }; EF29F1BB21271A9A00BE94E6 /* GroupDetailsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF29F1BA21271A9A00BE94E6 /* GroupDetailsViewControllerTests.swift */; }; EF2A8DE7214A816D002C9058 /* StartUIViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF2A8DE6214A816D002C9058 /* StartUIViewControllerSnapshotTests.swift */; }; EF2A8DE9214A8B38002C9058 /* MockUser+Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF2A8DE8214A8B38002C9058 /* MockUser+Service.swift */; }; - EF2A8DEB214A9AFD002C9058 /* ContactsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF2A8DEA214A9AFD002C9058 /* ContactsCell.swift */; }; EF2A8DED214AA36E002C9058 /* SeparatorViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF2A8DEC214AA36E002C9058 /* SeparatorViewProtocol.swift */; }; - EF2A8DEF214AAB13002C9058 /* ContactsCellSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF2A8DEE214AAB13002C9058 /* ContactsCellSnapshotTests.swift */; }; EF2B0DD121CA8EF1003B2882 /* url.json in Resources */ = {isa = PBXBuildFile; fileRef = EF2B0DD021CA8DF7003B2882 /* url.json */; }; EF2B255D22098C3D006712CA /* RemoveClientStepViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF2B255C22098C3D006712CA /* RemoveClientStepViewControllerSnapshotTests.swift */; }; EF2B255F2209C543006712CA /* UIUserInterfaceSizeClass+Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF2B255E2209C542006712CA /* UIUserInterfaceSizeClass+Constraint.swift */; }; @@ -1702,7 +1694,6 @@ EF3BA5D421075FC60093048F /* ConversationInputBarViewController+Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF3BA5D321075FC60093048F /* ConversationInputBarViewController+Language.swift */; }; EF3BA5D72107688D0093048F /* InputLanguageSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF3BA5D62107688D0093048F /* InputLanguageSettable.swift */; }; EF3BD69D225CC53B00ABB9B6 /* PermissionDeniedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF3BD69B225CC53B00ABB9B6 /* PermissionDeniedViewController.swift */; }; - EF3BD6A0225CD4FE00ABB9B6 /* ShareContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF3BD69E225CD4FE00ABB9B6 /* ShareContactsViewController.swift */; }; EF3CB75321DD0F0C0030C3E8 /* ConversationViewController+ParticipantsPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF3CB75221DD0F0B0030C3E8 /* ConversationViewController+ParticipantsPopover.swift */; }; EF3CB75521DD157B0030C3E8 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF3CB75421DD157B0030C3E8 /* ProfileViewController.swift */; }; EF3CBC0A2147D81800566295 /* ConversationListViewController+PushPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF3CBC092147D81700566295 /* ConversationListViewController+PushPermissions.swift */; }; @@ -1737,7 +1728,6 @@ EF68E71B20BD5B5A003F3594 /* IPadPopoverConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF68E71A20BD5B5A003F3594 /* IPadPopoverConstants.swift */; }; EF6A33D32192F5BD00970698 /* CoreDataSnapshotTestCase+appendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6A33D12192F4D400970698 /* CoreDataSnapshotTestCase+appendMessage.swift */; }; EF6ACA7A213169540064047B /* ConfirmAssetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6ACA79213169540064047B /* ConfirmAssetViewController.swift */; }; - EF6DAD6A2150F2C400D9BA70 /* ContactsViewController+SearchHeaderViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6DAD692150F2C400D9BA70 /* ContactsViewController+SearchHeaderViewControllerDelegate.swift */; }; EF6E3C7A2089F232003F6752 /* animated.gif in Resources */ = {isa = PBXBuildFile; fileRef = EF6E3C792089F231003F6752 /* animated.gif */; }; EF6E3C7C208A02FE003F6752 /* GiphySearchViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6E3C7B208A02FE003F6752 /* GiphySearchViewControllerSnapshotTests.swift */; }; EF6E3C7E208A0355003F6752 /* MockConversation+factory.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6E3C7D208A0355003F6752 /* MockConversation+factory.swift */; }; @@ -1747,8 +1737,6 @@ EF6EB1CE20A5C296002F720D /* AppStateCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6EB1CD20A5C296002F720D /* AppStateCalculatorTests.swift */; }; EF71BFD420EBC39000894A7C /* ConversationInputBarViewController+Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF71BFD320EBC39000894A7C /* ConversationInputBarViewController+Location.swift */; }; EF71BFD620EBCC5F00894A7C /* UIView+IPadPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF71BFD520EBCC5F00894A7C /* UIView+IPadPopover.swift */; }; - EF7E96EE214BB41800475C63 /* ContactsViewController+ContactsDataSourceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF7E96ED214BB41800475C63 /* ContactsViewController+ContactsDataSourceDelegate.swift */; }; - EF7E96F0214BFBE100475C63 /* ContactsViewController+UITableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF7E96EF214BFBE100475C63 /* ContactsViewController+UITableViewDelegate.swift */; }; EF7ECE1B211111B4003786C6 /* XCTestCase+FullscreenImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF7ECE1A211111B2003786C6 /* XCTestCase+FullscreenImageViewController.swift */; }; EF7F74041FCC033D0059C13F /* EmailAdresssValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF7F74021FCC01140059C13F /* EmailAdresssValidatorTests.swift */; }; EF7F8FC121B5761C00D7723C /* ConversationReadReceiptSettingChangedCellDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF7F8FC021B5761C00D7723C /* ConversationReadReceiptSettingChangedCellDescription.swift */; }; @@ -1804,10 +1792,6 @@ EFDBA24722942FBB00874A15 /* UIAlertViewController+CompanyLoginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFDBA24622942FBB00874A15 /* UIAlertViewController+CompanyLoginTests.swift */; }; EFDC36DA2257A63000F916F3 /* CollectionViewContainerCellSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFDC36D92257A63000F916F3 /* CollectionViewContainerCellSnapshotTests.swift */; }; EFDC36DB2257A8F300F916F3 /* CollectionViewContainerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87DCF4C91D34EA4500BB420F /* CollectionViewContainerCell.swift */; }; - EFDC36DC2257ACE800F916F3 /* StartUIInviteActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87DCF4D71D34EA4500BB420F /* StartUIInviteActionBar.swift */; }; - EFDE37FD213994750095DCA4 /* ContactsSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFDE37FB213994750095DCA4 /* ContactsSectionHeaderView.swift */; }; - EFE043992149CE8D00BC3079 /* ContactsViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFE043972149CE7600BC3079 /* ContactsViewControllerSnapshotTests.swift */; }; - EFE0439B2149D9CA00BC3079 /* ShareContactsViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFE0439A2149D9C900BC3079 /* ShareContactsViewControllerSnapshotTests.swift */; }; EFE6151C2260E4AA006D48ED /* MultiParagraphSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFE6151B2260E4AA006D48ED /* MultiParagraphSnapshotTests.swift */; }; EFE806301FE006A7006A0EE7 /* DateFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFE8062E1FE005A7006A0EE7 /* DateFormatterTests.swift */; }; EFEC285F233A6FF8007F47FF /* ConversationListHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFEC285E233A6FF8007F47FF /* ConversationListHeaderView.swift */; }; @@ -2009,12 +1993,11 @@ 016BDDEF2AA0AE670054FB04 /* Developer-Flags.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Developer-Flags.xcconfig"; sourceTree = ""; }; 016DB6DD2C261A4900DEB81B /* WireDomain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireDomain.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 01874DD62C18581C00208716 /* DeveloperToolsContextItemsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperToolsContextItemsProvider.swift; sourceTree = ""; }; - 01A5E043297FDAB500624B65 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 01A5E047297FDAB500624B65 /* updateStylekit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = updateStylekit; sourceTree = ""; }; 01A5E048297FDAB500624B65 /* copyExtraAudioNotifications */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = copyExtraAudioNotifications; sourceTree = ""; }; 01BC68502CE3AF9E00445243 /* EmptyConversationSearchResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyConversationSearchResultsView.swift; sourceTree = ""; }; 01BC68642CE496A500445243 /* EmptyPlaceholderContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyPlaceholderContainerView.swift; sourceTree = ""; }; - 01C488AF2C8A45F80066789E /* WireAnalytics_DatadogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireAnalytics_DatadogTests.swift; sourceTree = ""; }; + 01D2C78E2CECC41D00F05E5E /* QRCodeScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerViewController.swift; sourceTree = ""; }; 01D8E7192BA39CE900B71CB7 /* PrivacyWarningChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyWarningChecker.swift; sourceTree = ""; }; 01F5EAE32B712DD6009FD25D /* LogFileDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogFileDestination.swift; sourceTree = ""; }; 06024ABB29E69A850032CC31 /* ConversationMessageFailedRecipientsCellDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMessageFailedRecipientsCellDescription.swift; sourceTree = ""; }; @@ -2253,7 +2236,6 @@ 1E2268051B3957A7003980E3 /* conversations-01.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "conversations-01.json"; path = "PeoplePickerControllerTests/conversations-01.json"; sourceTree = ""; }; 1E5E0CAD1B6B83C6003B0CAC /* ButtonWithLargerHitArea.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonWithLargerHitArea.swift; sourceTree = ""; }; 1E8772271B0C8CAB005BDDC6 /* emo-test-03.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "emo-test-03.json"; sourceTree = ""; }; - 1E98C81A1BD1719500C7A7A2 /* ContactsDataSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsDataSourceTests.swift; sourceTree = ""; }; 1EE9DCB01B5F9A6D00E347DF /* TokenTextAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenTextAttachment.swift; sourceTree = ""; }; 1EE9DCB11B5F9A6D00E347DF /* TokenizedTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenizedTextView.swift; sourceTree = ""; }; 1EFB23DD1ACEDE5400244571 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = Base; path = Base.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -2310,7 +2292,6 @@ 54AA3C7B24E53E0100FE1F94 /* Security-Flags.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Security-Flags.xcconfig"; sourceTree = ""; }; 54B27985258113FD0043ED25 /* AuthenticatedRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatedRouter.swift; sourceTree = ""; }; 54D355DF25559BFC0004DAC7 /* ActiveCallRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveCallRouter.swift; sourceTree = ""; }; - 54F7C27E1D74367E004D8087 /* AddressBookHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressBookHelper.swift; sourceTree = ""; }; 54FF9E271E813BA500323D2E /* DebugAlert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugAlert.swift; sourceTree = ""; }; 5501FFA922B28D210050D8FC /* MockSelfLegalHoldSubject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSelfLegalHoldSubject.swift; sourceTree = ""; }; 5501FFC822B398B20050D8FC /* UserInputRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInputRequest.swift; sourceTree = ""; }; @@ -2322,10 +2303,8 @@ 55DD5C331FA236F60082170C /* CallQualityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallQualityViewController.swift; sourceTree = ""; }; 5902F8D62BF78FC200F1D392 /* ArchivedListViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchivedListViewControllerDelegate.swift; sourceTree = ""; }; 5902F8DF2BF7B14F00F1D392 /* ArchivedListViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArchivedListViewControllerSnapshotTests.swift; sourceTree = ""; }; - 590A5F062BF4CB1B008E87D8 /* PermissionDeniedViewController+addressBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PermissionDeniedViewController+addressBook.swift"; sourceTree = ""; }; 590A5F082BF4CB6B008E87D8 /* PermissionDeniedViewController+notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PermissionDeniedViewController+notifications.swift"; sourceTree = ""; }; 590A5F0A2BF4CBCE008E87D8 /* PermissionDeniedViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionDeniedViewControllerDelegate.swift; sourceTree = ""; }; - 590BD7B72C00BD1C0093435A /* ShareContactsViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareContactsViewControllerDelegate.swift; sourceTree = ""; }; 5915B94A2BF4A70900215817 /* ShouldPresentNotificationPermissionHintUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShouldPresentNotificationPermissionHintUseCaseProtocol.swift; sourceTree = ""; }; 5915B94C2BF4A76C00215817 /* ShouldPresentNotificationPermissionHintUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShouldPresentNotificationPermissionHintUseCase.swift; sourceTree = ""; }; 5915B9552BF4B8AC00215817 /* ShouldPresentNotificationPermissionHintUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShouldPresentNotificationPermissionHintUseCaseTests.swift; sourceTree = ""; }; @@ -2728,7 +2707,6 @@ 8711A59F1E0C320100043ACF /* FloatingPoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatingPoint.swift; sourceTree = ""; }; 8711A5A11E0D395A00043ACF /* CollectionCellHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionCellHeader.swift; sourceTree = ""; }; 8711A5A31E0D6C8800043ACF /* TwoLineTitleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoLineTitleView.swift; sourceTree = ""; }; - 8712134A1FA37881008B6767 /* TrackingManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackingManager.swift; sourceTree = ""; }; 8719FD661C88802C005FBEC0 /* Delay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Delay.swift; sourceTree = ""; }; 871BC0251D34F5D200DF0793 /* EmoticonSubstitutionConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmoticonSubstitutionConfiguration.swift; sourceTree = ""; }; 871BC1441D34F8F800DF0793 /* RotationAwareNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RotationAwareNavigationController.swift; sourceTree = ""; }; @@ -2889,14 +2867,12 @@ 87D5E4411C3E79C600EB8289 /* Data+Fingerprint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Fingerprint.swift"; sourceTree = ""; }; 87D679E51EBC8002005BEB4C /* String+Compatibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Compatibility.swift"; sourceTree = ""; }; 87D7E1C420CACA820064C03D /* CallViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallViewControllerTests.swift; sourceTree = ""; }; - 87DCF48C1D34E9BA00BB420F /* ZMAddressBookContact+LocalInvitation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ZMAddressBookContact+LocalInvitation.swift"; sourceTree = ""; }; 87DCF4961D34E9E600BB420F /* LocationSelectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationSelectionViewController.swift; sourceTree = ""; }; 87DCF4971D34E9E600BB420F /* LocationSendViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationSendViewController.swift; sourceTree = ""; }; 87DCF4981D34E9E600BB420F /* MapKit+Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MapKit+Helper.swift"; sourceTree = ""; }; 87DCF49A1D34E9E600BB420F /* NSUserDefaults+Location.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSUserDefaults+Location.swift"; sourceTree = ""; }; 87DCF4B61D34EA4500BB420F /* ShareItemProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareItemProvider.swift; sourceTree = ""; }; 87DCF4C91D34EA4500BB420F /* CollectionViewContainerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewContainerCell.swift; sourceTree = ""; }; - 87DCF4D71D34EA4500BB420F /* StartUIInviteActionBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StartUIInviteActionBar.swift; sourceTree = ""; }; 87DE06B21D26632E0009411F /* AudioEffectsPickerViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioEffectsPickerViewControllerTests.swift; sourceTree = ""; }; 87E4D2C91FC6E39800F3C664 /* CharacterInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterInputField.swift; sourceTree = ""; }; 87E56ED71E4E10CA0097D489 /* TextSearchResultCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextSearchResultCell.swift; sourceTree = ""; }; @@ -2983,7 +2959,6 @@ A9712D5C2743C4640008F380 /* GiphyConfirmationViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiphyConfirmationViewControllerSnapshotTests.swift; sourceTree = ""; }; A97192B2221302DD00E72AD1 /* MockUserRight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserRight.swift; sourceTree = ""; }; A97284072384244E00AFEFC6 /* BackupExcluderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupExcluderTests.swift; sourceTree = ""; }; - A9730DD9250274150061BF36 /* CountlyInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountlyInstance.swift; sourceTree = ""; }; A9730DDB250287360061BF36 /* Bundle+ThirdPartyKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+ThirdPartyKeys.swift"; sourceTree = ""; }; A97559F32729AA7100217714 /* TextSearchResultsViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextSearchResultsViewSnapshotTests.swift; sourceTree = ""; }; A978D992236C587B00B568AE /* ZMUserSession+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMUserSession+Shared.swift"; sourceTree = ""; }; @@ -3038,7 +3013,6 @@ A9E674D424F508B70058FC72 /* WireEmail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireEmail.swift; sourceTree = ""; }; A9E674D624F514700058FC72 /* email.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = email.json; sourceTree = ""; }; A9E674D924F51A190058FC72 /* WireEmailTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireEmailTests.swift; sourceTree = ""; }; - A9EB722223C1581A00DA8240 /* ContactsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsViewController.swift; sourceTree = ""; }; A9EB722423C3A47B00DA8240 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; A9EB722723C3A9F600DA8240 /* ConversationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationViewController.swift; sourceTree = ""; }; A9F57E3C2419A83100EEA912 /* TokenContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenContainer.swift; sourceTree = ""; }; @@ -3244,6 +3218,7 @@ CB366A8F2CC7DE410083701F /* ArchivedListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchivedListViewModelTests.swift; sourceTree = ""; }; CB43DBC32CE639E800BF5AEB /* FolderPickerViewControllerBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderPickerViewControllerBuilder.swift; sourceTree = ""; }; CB4870F12C7F4FE5001E9151 /* WireTransportSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireTransportSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CB8E51E22CF89D9B00F7F01D /* ConversationFilterSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationFilterSelector.swift; sourceTree = ""; }; CE06C93E1DF5C3D900497685 /* AVAsset+VideoConvert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVAsset+VideoConvert.swift"; sourceTree = ""; }; CE8E4FA91DF066750009F437 /* FileMetaDataGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileMetaDataGenerator.swift; sourceTree = ""; }; CE8E4FB21DF066EE0009F437 /* AudioProcessing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioProcessing.swift; sourceTree = ""; }; @@ -3407,7 +3382,6 @@ E98B610F2B5820BD0030E021 /* SwiftMockConversation+ConversationCreation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftMockConversation+ConversationCreation.swift"; sourceTree = ""; }; E9929EF62BCEC6FB00C40274 /* AppLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLocationManager.swift; sourceTree = ""; }; E9929EF82BCEDE5900C40274 /* MapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = ""; }; - E996C35B2CA2FA05008924A4 /* TrackingManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingManagerError.swift; sourceTree = ""; }; E9A60C7229716A70001D0E14 /* UITextField+DoneBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+DoneBarButtonItem.swift"; sourceTree = ""; }; E9ACC8352C10884F0002E608 /* StartUIIconCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartUIIconCell.swift; sourceTree = ""; }; E9ACC8372C1089D10002E608 /* OpenServicesAdminCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenServicesAdminCell.swift; sourceTree = ""; }; @@ -3438,7 +3412,6 @@ E9D6C37C289A9DC100861E4C /* StylableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StylableButton.swift; sourceTree = ""; }; E9D70C882BFDDDDB00D77C2C /* BasicReactionPickerSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicReactionPickerSnapshotTests.swift; sourceTree = ""; }; E9DA4684286C547A00F47C14 /* UIWindow+Orientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIWindow+Orientation.swift"; sourceTree = ""; }; - E9DA6FF22C5CCBC600BF8298 /* TrackingManager+UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrackingManager+UIAlertController.swift"; sourceTree = ""; }; E9DD582A2C53929400645A2F /* ChangeEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeEmailViewModel.swift; sourceTree = ""; }; E9DD582C2C539B3C00645A2F /* ChangeEmailViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeEmailViewModelTests.swift; sourceTree = ""; }; E9DD582E2C539D0C00645A2F /* ChangeEmailError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeEmailError.swift; sourceTree = ""; }; @@ -3494,7 +3467,6 @@ EE593F9B2B73C3F0008D1109 /* DeepLinksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinksView.swift; sourceTree = ""; }; EE5F54D0259B77DD00F11F3C /* Viper+Interfaces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Viper+Interfaces.swift"; sourceTree = ""; }; EE5F54D1259B77DD00F11F3C /* Viper+Relationships.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Viper+Relationships.swift"; sourceTree = ""; }; - EE67F6CE296F067F001D7C88 /* Countly.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Countly.xcframework; path = ../Carthage/Build/Countly.xcframework; sourceTree = ""; }; EE67F6D7296F06B3001D7C88 /* FLAnimatedImage.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = FLAnimatedImage.xcframework; path = ../Carthage/Build/FLAnimatedImage.xcframework; sourceTree = ""; }; EE67F739296F0E28001D7C88 /* WireSyncEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireSyncEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE67F73D296F0E7D001D7C88 /* Ziphy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Ziphy.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -3520,7 +3492,6 @@ EE88E00523FAC9EA00D24703 /* SelfUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfUser.swift; sourceTree = ""; }; EE8E9266202496C2000F4752 /* NSAttributedString+Down.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Down.swift"; sourceTree = ""; }; EE8E926D2024F085000F4752 /* MarkdownTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownTextView.swift; sourceTree = ""; }; - EE948F1823E17778000A663E /* ContactsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsDataSource.swift; sourceTree = ""; }; EE94CA0B25C2E48B005F1155 /* ScreenCurtainWindowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenCurtainWindowTests.swift; sourceTree = ""; }; EE961C1F2A9F036A008127A6 /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = ""; }; EE98A09D296ED46D0055E981 /* DeviceConfigurationEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConfigurationEventHandler.swift; sourceTree = ""; }; @@ -3549,7 +3520,6 @@ EECA90AB28744A060024B301 /* DeveloperFlagsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperFlagsViewModel.swift; sourceTree = ""; }; EECDB2552029F5AB00D0D268 /* MarkdownTextStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownTextStorage.swift; sourceTree = ""; }; EED92352278DA9D2002D4D9D /* AccentColorPickerSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AccentColorPickerSnapshotTests.swift; path = "Wire-iOS Tests/AccentColorPickerSnapshotTests.swift"; sourceTree = SOURCE_ROOT; }; - EEDEA26723ED609C000EF7E3 /* ContactsViewController+Invite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactsViewController+Invite.swift"; sourceTree = ""; }; EEE104042126F68A003C779B /* CameraController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraController.swift; sourceTree = ""; }; EEE25B2629719A730008B894 /* Clibsodium.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Clibsodium.xcframework; path = ../Carthage/Build/Clibsodium.xcframework; sourceTree = ""; }; EEE25B2729719A730008B894 /* cryptobox.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = cryptobox.xcframework; path = ../Carthage/Build/cryptobox.xcframework; sourceTree = ""; }; @@ -3603,7 +3573,6 @@ EEFAB4992AB83AC5003140DF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Emoji.strings; sourceTree = ""; }; EEFF3E7B21B6D1FB000834A6 /* ConversationCreateSectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationCreateSectionController.swift; sourceTree = ""; }; EF00FD731FDA97BA0059FEDC /* UIViewController+Orientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Orientation.swift"; sourceTree = ""; }; - EF0154B6225B922200A77FB6 /* ContactsViewController+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactsViewController+Constraints.swift"; sourceTree = ""; }; EF01D27F22B136A9009754B2 /* Data+FingerprintStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+FingerprintStringTests.swift"; sourceTree = ""; }; EF05948322DF07240079B8E1 /* XCTestCase+AccentColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+AccentColor.swift"; sourceTree = ""; }; EF05948522DF18290079B8E1 /* TeamAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamAccountView.swift; sourceTree = ""; }; @@ -3678,15 +3647,12 @@ EF282B93228AFA970042D6C5 /* RequestPasswordViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestPasswordViewControllerSnapshotTests.swift; sourceTree = ""; }; EF2971552099AD3000EFE6A4 /* SelfProfileViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfProfileViewControllerTests.swift; sourceTree = ""; }; EF298BD0228D942A0078BE68 /* ImageManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageManagerProtocol.swift; sourceTree = ""; }; - EF298BD2228D9BB20078BE68 /* StartUIViewController+AddressBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StartUIViewController+AddressBook.swift"; sourceTree = ""; }; EF29CDC122D3907000EB2380 /* ConversationListViewControllerViewModel+ConversationListContentDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationListViewControllerViewModel+ConversationListContentDelegate.swift"; sourceTree = ""; }; EF29E89B212302CD0023B80C /* AudioMessageViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMessageViewTests.swift; sourceTree = ""; }; EF29F1BA21271A9A00BE94E6 /* GroupDetailsViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupDetailsViewControllerTests.swift; sourceTree = ""; }; EF2A8DE6214A816D002C9058 /* StartUIViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartUIViewControllerSnapshotTests.swift; sourceTree = ""; }; EF2A8DE8214A8B38002C9058 /* MockUser+Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockUser+Service.swift"; sourceTree = ""; }; - EF2A8DEA214A9AFD002C9058 /* ContactsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsCell.swift; sourceTree = ""; }; EF2A8DEC214AA36E002C9058 /* SeparatorViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorViewProtocol.swift; sourceTree = ""; }; - EF2A8DEE214AAB13002C9058 /* ContactsCellSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsCellSnapshotTests.swift; sourceTree = ""; }; EF2B0DD021CA8DF7003B2882 /* url.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = url.json; sourceTree = ""; }; EF2B255C22098C3D006712CA /* RemoveClientStepViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveClientStepViewControllerSnapshotTests.swift; sourceTree = ""; }; EF2B255E2209C542006712CA /* UIUserInterfaceSizeClass+Constraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIUserInterfaceSizeClass+Constraint.swift"; sourceTree = ""; }; @@ -3715,7 +3681,6 @@ EF3BA5D321075FC60093048F /* ConversationInputBarViewController+Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationInputBarViewController+Language.swift"; sourceTree = ""; }; EF3BA5D62107688D0093048F /* InputLanguageSettable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputLanguageSettable.swift; sourceTree = ""; }; EF3BD69B225CC53B00ABB9B6 /* PermissionDeniedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionDeniedViewController.swift; sourceTree = ""; }; - EF3BD69E225CD4FE00ABB9B6 /* ShareContactsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareContactsViewController.swift; sourceTree = ""; }; EF3CB75221DD0F0B0030C3E8 /* ConversationViewController+ParticipantsPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationViewController+ParticipantsPopover.swift"; sourceTree = ""; }; EF3CB75421DD157B0030C3E8 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; EF3CBC092147D81700566295 /* ConversationListViewController+PushPermissions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConversationListViewController+PushPermissions.swift"; sourceTree = ""; }; @@ -3752,7 +3717,6 @@ EF68E71A20BD5B5A003F3594 /* IPadPopoverConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPadPopoverConstants.swift; sourceTree = ""; }; EF6A33D12192F4D400970698 /* CoreDataSnapshotTestCase+appendMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreDataSnapshotTestCase+appendMessage.swift"; sourceTree = ""; }; EF6ACA79213169540064047B /* ConfirmAssetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmAssetViewController.swift; sourceTree = ""; }; - EF6DAD692150F2C400D9BA70 /* ContactsViewController+SearchHeaderViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactsViewController+SearchHeaderViewControllerDelegate.swift"; sourceTree = ""; }; EF6E3C792089F231003F6752 /* animated.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = animated.gif; sourceTree = ""; }; EF6E3C7B208A02FE003F6752 /* GiphySearchViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiphySearchViewControllerSnapshotTests.swift; sourceTree = ""; }; EF6E3C7D208A0355003F6752 /* MockConversation+factory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MockConversation+factory.swift"; sourceTree = ""; }; @@ -3762,8 +3726,6 @@ EF6EB1CD20A5C296002F720D /* AppStateCalculatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateCalculatorTests.swift; sourceTree = ""; }; EF71BFD320EBC39000894A7C /* ConversationInputBarViewController+Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationInputBarViewController+Location.swift"; sourceTree = ""; }; EF71BFD520EBCC5F00894A7C /* UIView+IPadPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+IPadPopover.swift"; sourceTree = ""; }; - EF7E96ED214BB41800475C63 /* ContactsViewController+ContactsDataSourceDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ContactsViewController+ContactsDataSourceDelegate.swift"; sourceTree = ""; }; - EF7E96EF214BFBE100475C63 /* ContactsViewController+UITableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactsViewController+UITableViewDelegate.swift"; sourceTree = ""; }; EF7ECE1A211111B2003786C6 /* XCTestCase+FullscreenImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+FullscreenImageViewController.swift"; sourceTree = ""; }; EF7F74021FCC01140059C13F /* EmailAdresssValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAdresssValidatorTests.swift; sourceTree = ""; }; EF7F8FC021B5761C00D7723C /* ConversationReadReceiptSettingChangedCellDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationReadReceiptSettingChangedCellDescription.swift; sourceTree = ""; }; @@ -3813,9 +3775,6 @@ EFDBA24322941D1800874A15 /* UIAccessibilityIdentification+LegalHold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAccessibilityIdentification+LegalHold.swift"; sourceTree = ""; }; EFDBA24622942FBB00874A15 /* UIAlertViewController+CompanyLoginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertViewController+CompanyLoginTests.swift"; sourceTree = ""; }; EFDC36D92257A63000F916F3 /* CollectionViewContainerCellSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewContainerCellSnapshotTests.swift; sourceTree = ""; }; - EFDE37FB213994750095DCA4 /* ContactsSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsSectionHeaderView.swift; sourceTree = ""; }; - EFE043972149CE7600BC3079 /* ContactsViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsViewControllerSnapshotTests.swift; sourceTree = ""; }; - EFE0439A2149D9C900BC3079 /* ShareContactsViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareContactsViewControllerSnapshotTests.swift; sourceTree = ""; }; EFE6151B2260E4AA006D48ED /* MultiParagraphSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiParagraphSnapshotTests.swift; sourceTree = ""; }; EFE8062E1FE005A7006A0EE7 /* DateFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatterTests.swift; sourceTree = ""; }; EFEC285E233A6FF8007F47FF /* ConversationListHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationListHeaderView.swift; sourceTree = ""; }; @@ -3902,8 +3861,22 @@ F1FEA14D21DCEB1700790A54 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 594346032D08647F00A6C0B5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + TrackingManager.swift, + "TrackingManager+UIAlertController.swift", + TrackingManagerError.swift, + ); + target = 8F42C537199244A700288E4D /* Wire-iOS */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + /* Begin PBXFileSystemSynchronizedRootGroup section */ + 594345FF2D08647F00A6C0B5 /* AnalyticsTrackingManager */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (594346032D08647F00A6C0B5 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = AnalyticsTrackingManager; sourceTree = ""; }; 595C04172CDE85E800DF6C57 /* MainController */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = MainController; sourceTree = ""; }; + 5983A3EE2CF86F9A002006F6 /* Logging */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Logging; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -3912,6 +3885,7 @@ buildActionMask = 2147483647; files = ( 591B6E212C8B096C009F8A7B /* WireCommonComponents.framework in Frameworks */, + 59537D912CFFA0BA00920B59 /* WireLogging in Frameworks */, EE9A8586298B0A3B00064A9C /* WireNotificationEngine.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3922,6 +3896,7 @@ files = ( 591B6E192C8B0960009F8A7B /* WireCommonComponents.framework in Frameworks */, 59C4FBF12C45B7130037030B /* WireShareEngine.framework in Frameworks */, + 59537D932CFFA0DA00920B59 /* WireLogging in Frameworks */, 59CDB3F62C4EA08F0049D1AB /* WireReusableUIComponents in Frameworks */, 5945D02A2C219F7200D039E3 /* WireDesign in Frameworks */, ); @@ -3943,13 +3918,14 @@ EE67F73E296F0E7D001D7C88 /* Ziphy.framework in Frameworks */, CB4E15122C81CC81005DDEC8 /* Down in Frameworks */, EEE25B3829719C170008B894 /* WireDataModel.framework in Frameworks */, - E92C8CC62C942A5C00E0F7F7 /* Inject in Frameworks */, 59D038272C85D31E009FE583 /* WireMainNavigationUI in Frameworks */, EE33C48D296485FA00C058D1 /* WireShareEngine.framework in Frameworks */, EEE25B2A29719A730008B894 /* cryptobox.xcframework in Frameworks */, EEE25B3E29719C580008B894 /* WireProtos.framework in Frameworks */, 59076F952C934A9800AE7529 /* WireAccountImageUI in Frameworks */, 590DCA082C971A56002D0A2C /* WireSidebarUI in Frameworks */, + 594341D72D08611700A6C0B5 /* WireCountly in Frameworks */, + 594906A52D0713C000238104 /* WireAnalytics in Frameworks */, EEE25B5029719D2F0008B894 /* avs.xcframework in Frameworks */, 59B99FAA2C89DE8600201827 /* WireFoundation in Frameworks */, 59B99FAC2C89DF2100201827 /* WireAPI in Frameworks */, @@ -3960,12 +3936,14 @@ 407831AD2AC4636F005C2978 /* DifferenceKit in Frameworks */, EEE25B4429719C950008B894 /* WireSystem.framework in Frameworks */, 591B6E172C8B095B009F8A7B /* WireNotificationEngine.framework in Frameworks */, + 59537D952CFFA11A00920B59 /* WireLogging in Frameworks */, E9FBF3A62C47C23100C65DA8 /* FLAnimatedImage in Frameworks */, 59CD83E52C22CE9800186022 /* WireDesign in Frameworks */, E9BA75C62CD51DF100F6EDDF /* WireMoveToFolderUI in Frameworks */, EEE25B4D29719D0B0008B894 /* ZipArchive.xcframework in Frameworks */, 061F34192B1E2ED50099CBAB /* AppAuth in Frameworks */, 061F341B2B1E2ED50099CBAB /* AppAuthCore in Frameworks */, + 76D15E712CEFAE8A0059215D /* WireIndividualToTeamMigrationUI in Frameworks */, 016DB6DE2C261A4900DEB81B /* WireDomain.framework in Frameworks */, 5996E8A62C19D090007A52F0 /* WireSyncEngine.framework in Frameworks */, 59B99FB02C89DFB700201827 /* WireDomainPackage in Frameworks */, @@ -3987,6 +3965,7 @@ 5996E8A42C19D074007A52F0 /* WireRequestStrategySupport.framework in Frameworks */, 598E86F72BF4DD5C00FC5438 /* WireSystemSupport.framework in Frameworks */, 598E86D12BF4D97800FC5438 /* WireUtilitiesSupport.framework in Frameworks */, + 59191A652D0051C7001AB388 /* WireLogging in Frameworks */, E9816C9A2CC929FC00D77F22 /* WireFoundationSupport in Frameworks */, 590DCA0A2C971AFF002D0A2C /* WireFoundation in Frameworks */, 591B6E1C2C8B0964009F8A7B /* WireDataModelSupport.framework in Frameworks */, @@ -4016,6 +3995,7 @@ buildActionMask = 2147483647; files = ( E985CB8F2CEB4FCB0075DAD6 /* WireDatadog in Frameworks */, + 59537D8F2CFFA05600920B59 /* WireLogging in Frameworks */, E9816C902CC9244700D77F22 /* WireSyncEngine.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4432,7 +4412,6 @@ 1E2267EB1B382F1F003980E3 /* PeoplePickerTests */ = { isa = PBXGroup; children = ( - 1E98C81A1BD1719500C7A7A2 /* ContactsDataSourceTests.swift */, DBF7235E1B5566B80036F4C5 /* a_lot_of_people.json */, EF883F2A21511DD700B440CC /* people-15Sections.json */, 1E2268031B395722003980E3 /* people-01.json */, @@ -4603,15 +4582,6 @@ path = ConversationList; sourceTree = ""; }; - 590A5F052BF4CB06008E87D8 /* ShareContacts */ = { - isa = PBXGroup; - children = ( - EF3BD69E225CD4FE00ABB9B6 /* ShareContactsViewController.swift */, - 590BD7B72C00BD1C0093435A /* ShareContactsViewControllerDelegate.swift */, - ); - path = ShareContacts; - sourceTree = ""; - }; 5915B9492BF4A62100215817 /* NotificationAuthorizationStatusDeniedUseCases */ = { isa = PBXGroup; children = ( @@ -5533,7 +5503,6 @@ EE67F73D296F0E7D001D7C88 /* Ziphy.framework */, EEE25B4F29719D2F0008B894 /* avs.xcframework */, EEE25B2629719A730008B894 /* Clibsodium.xcframework */, - EE67F6CE296F067F001D7C88 /* Countly.xcframework */, EEE25B2729719A730008B894 /* cryptobox.xcframework */, EE67F6D7296F06B3001D7C88 /* FLAnimatedImage.xcframework */, EEE25B5229719D540008B894 /* libPhoneNumberiOS.xcframework */, @@ -5569,15 +5538,6 @@ path = CellDescriptors; sourceTree = ""; }; - 871BBFA11D34F56300DF0793 /* Analytics */ = { - isa = PBXGroup; - children = ( - E9CD10612C66418E0060D40D /* TrackingManager */, - A9730DD9250274150061BF36 /* CountlyInstance.swift */, - ); - path = Analytics; - sourceTree = ""; - }; 871BC1271D34F8F800DF0793 /* Components */ = { isa = PBXGroup; children = ( @@ -6096,7 +6056,6 @@ isa = PBXGroup; children = ( EF3BD69B225CC53B00ABB9B6 /* PermissionDeniedViewController.swift */, - 590A5F062BF4CB1B008E87D8 /* PermissionDeniedViewController+addressBook.swift */, 590A5F082BF4CB6B008E87D8 /* PermissionDeniedViewController+notifications.swift */, 590A5F0A2BF4CBCE008E87D8 /* PermissionDeniedViewControllerDelegate.swift */, 5915B9492BF4A62100215817 /* NotificationAuthorizationStatusDeniedUseCases */, @@ -6104,23 +6063,6 @@ path = PermissionDeniedHint; sourceTree = ""; }; - 87DCF47B1D34E9BA00BB420F /* ContactsUI */ = { - isa = PBXGroup; - children = ( - EF2A8DEA214A9AFD002C9058 /* ContactsCell.swift */, - EE948F1823E17778000A663E /* ContactsDataSource.swift */, - EFDE37FB213994750095DCA4 /* ContactsSectionHeaderView.swift */, - A9EB722223C1581A00DA8240 /* ContactsViewController.swift */, - EEDEA26723ED609C000EF7E3 /* ContactsViewController+Invite.swift */, - EF6DAD692150F2C400D9BA70 /* ContactsViewController+SearchHeaderViewControllerDelegate.swift */, - EF0154B6225B922200A77FB6 /* ContactsViewController+Constraints.swift */, - EF7E96ED214BB41800475C63 /* ContactsViewController+ContactsDataSourceDelegate.swift */, - EF7E96EF214BFBE100475C63 /* ContactsViewController+UITableViewDelegate.swift */, - 87DCF48C1D34E9BA00BB420F /* ZMAddressBookContact+LocalInvitation.swift */, - ); - path = ContactsUI; - sourceTree = ""; - }; 87DCF4951D34E9E600BB420F /* Location */ = { isa = PBXGroup; children = ( @@ -6232,10 +6174,8 @@ isa = PBXGroup; children = ( 59E12BFD2CC8EDF200E687CE /* ConnectViewControllerBuilderProtocol.swift */, - 87DCF4D71D34EA4500BB420F /* StartUIInviteActionBar.swift */, A97FA6FD23B5883A005257A8 /* StartUIDelegate.swift */, A97FA6FB23B57067005257A8 /* StartUIViewController.swift */, - EF298BD2228D9BB20078BE68 /* StartUIViewController+AddressBook.swift */, E9ACC8392C108F390002E608 /* StartUIViewController+NavigationController.swift */, 7C6878D7201B3785003A0C7A /* StartUIViewController+SearchResults.swift */, 5965E9912C9BE151001D8AE1 /* StartUIViewControllerBuilder.swift */, @@ -6274,7 +6214,6 @@ isa = PBXGroup; children = ( EE9AEC9D2BD15A3600F7853F /* Wire-iOS.docc */, - 01A5E043297FDAB500624B65 /* README.md */, 01A5E045297FDAB500624B65 /* Scripts */, 87C8D7FB1BA8261C00B0530B /* Entitlements-Dev.entitlements */, 87C8D7FC1BA8261C00B0530B /* Entitlements-Prod.entitlements */, @@ -6382,7 +6321,7 @@ 871BC3181D34F94200DF0793 /* Components */, 8FC85126199245760008B66B /* Helpers */, E902B3B529ED3A2E0090DE32 /* SwiftUI Helpers */, - 871BBFA11D34F56300DF0793 /* Analytics */, + 594345FF2D08647F00A6C0B5 /* AnalyticsTrackingManager */, 8FC850F7199245760008B66B /* Developer */, 8FC85146199245760008B66B /* Managers */, EF2126B41FB9DFE300625A9B /* Authentication */, @@ -6527,7 +6466,6 @@ children = ( 16E2A0A11F6BEF6E005F4DB1 /* SoundEventListener.swift */, 16E2A09F1F6BEDE7005F4DB1 /* ProximityMonitorManager.swift */, - 54F7C27E1D74367E004D8087 /* AddressBookHelper.swift */, A9C33DFD23D0704F0069C8BB /* MediaPlaybackManager.swift */, BA79B0A41B6284D900F23AFB /* SoundEventRulesWatchDog.swift */, EF9FC3F02142AEDE00033ED0 /* ColorSchemeController.swift */, @@ -6542,7 +6480,6 @@ isa = PBXGroup; children = ( E9816C952CC9280400D77F22 /* LaunchScreenViewController.swift */, - 590A5F052BF4CB06008E87D8 /* ShareContacts */, 87DCF4741D34E95B00BB420F /* PermissionDeniedHint */, EEF37ECE2BDFC74C0055C9E1 /* SwitchBackend */, 061F34142B1A4F190099CBAB /* E2EIdentity */, @@ -6578,7 +6515,6 @@ 59A27BB12C2ACCC200195393 /* TopOverlayPresenter */, 87DCF4951D34E9E600BB420F /* Location */, 87DCF4A01D34EA4500BB420F /* StartUI */, - 87DCF47B1D34E9BA00BB420F /* ContactsUI */, 2572EC452B148EE50075A7BE /* DeviceView */, ); path = UserInterface; @@ -6752,6 +6688,7 @@ children = ( 59AF77A02CC7FB39002438D1 /* AnyMainCoordinator.swift */, 596184AC2CC7B5F600787AF0 /* DefaultSettingsPropertyFactoryDelegate.swift */, + CB8E51E22CF89D9B00F7F01D /* ConversationFilterSelector.swift */, 5965E9722C9B1491001D8AE1 /* ConversationListViewController+MainConversationListProtocol.swift */, 59E67CB12CA8C356000F1C17 /* ConversationRootViewController+MainConversationProtocol.swift */, 597CAEB22CA40111002A1160 /* MainCoordinator+ArchivedListViewControllerDelegate.swift */, @@ -7122,8 +7059,6 @@ BFA13E151D4A5F7100F0A91B /* ConfirmAssetViewControllerTests.swift */, EF3052442087388000613C45 /* ConfirmEmailViewControllerTests.swift */, EF17B0DA223A9B87006252A8 /* ConnectRequestsViewControllerSnapshotTests.swift */, - EF2A8DEE214AAB13002C9058 /* ContactsCellSnapshotTests.swift */, - EFE043972149CE7600BC3079 /* ContactsViewControllerSnapshotTests.swift */, EF17175822D4990800697EB0 /* ConversationAvatarViewModeTests.swift */, BFF655731E81235900D48337 /* ConversationAvatarViewTests.swift */, EF207B36220839E000738E84 /* ConversationContentViewControllerTests.swift */, @@ -7196,7 +7131,6 @@ E9D141642AC71719005EE75D /* CreateSecureGuestLinkViewModelTests.swift */, EF36168921E753300041F0CC /* SettingsTableViewControllerSnapshotTests.swift */, EFC0C7F021778FB000380C4B /* SettingsTextCellSnapshotTests.swift */, - EFE0439A2149D9C900BC3079 /* ShareContactsViewControllerSnapshotTests.swift */, 7CAB951120650EE6004BAFC4 /* ShareDestinationCellTests.swift */, 7C23A26B20247474005FEB54 /* ShareViewControllerTests.swift */, EF17B0D7223A7385006252A8 /* SketchColorPickerControllerSnapshotTests.swift */, @@ -7272,7 +7206,6 @@ A923F1C32428C562003F361F /* ZipFile */, 25CCE9D32BA1EA09002AB21F /* OtherUserDeviceDetailsViewTests.swift */, E95D482E2C57974500F342EE /* CharacterInputFieldSnapshotTests.swift */, - 01C488AF2C8A45F80066789E /* WireAnalytics_DatadogTests.swift */, ); path = "Wire-iOS Tests"; sourceTree = ""; @@ -7873,16 +7806,6 @@ path = EphemeralKeyboardViewController; sourceTree = ""; }; - E9CD10612C66418E0060D40D /* TrackingManager */ = { - isa = PBXGroup; - children = ( - E9DA6FF22C5CCBC600BF8298 /* TrackingManager+UIAlertController.swift */, - 8712134A1FA37881008B6767 /* TrackingManager.swift */, - E996C35B2CA2FA05008924A4 /* TrackingManagerError.swift */, - ); - path = TrackingManager; - sourceTree = ""; - }; E9D141632AC704E9005EE75D /* CreateSecureGuestLink */ = { isa = PBXGroup; children = ( @@ -7955,6 +7878,7 @@ children = ( EE593F9B2B73C3F0008D1109 /* DeepLinksView.swift */, EE593F992B73C3E6008D1109 /* DeepLinksViewModel.swift */, + 01D2C78E2CECC41D00F05E5E /* QRCodeScannerViewController.swift */, ); path = DeepLinks; sourceTree = ""; @@ -8505,6 +8429,7 @@ E63C65642C1B456900354BB4 /* Analytics */, 594211BD2C90542100576053 /* FilePreviewGenerator */, 594211CE2C906FEC00576053 /* FileMetaDataGenerator */, + 5983A3EE2CF86F9A002006F6 /* Logging */, CE8E4FB21DF066EE0009F437 /* AudioProcessing.swift */, EF1EB850235F008F00575DD9 /* Bundle+InfoDict.swift */, 5922D6D62BB56FA100A60408 /* Bundle+SecurityFlags.swift */, @@ -8600,6 +8525,7 @@ packageProductDependencies = ( 5945D0292C219F7200D039E3 /* WireDesign */, 59CDB3F52C4EA08F0049D1AB /* WireReusableUIComponents */, + 59537D922CFFA0DA00920B59 /* WireLogging */, ); productName = "Wire-iOS Share Extension"; productReference = 168A16A91D9597C2005CFA6C /* Wire Share Extension.appex */; @@ -8645,9 +8571,12 @@ 595C49682CA995E900F8F881 /* WireSettingsUI */, 59076F942C934A9800AE7529 /* WireAccountImageUI */, 590DCA072C971A56002D0A2C /* WireSidebarUI */, - E92C8CC52C942A5C00E0F7F7 /* Inject */, CB43D9BC2CDBBBB900BF5AEB /* WireFolderPickerUI */, E9BA75C52CD51DF100F6EDDF /* WireMoveToFolderUI */, + 59537D942CFFA11A00920B59 /* WireLogging */, + 76D15E702CEFAE8A0059215D /* WireIndividualToTeamMigrationUI */, + 594906A42D0713C000238104 /* WireAnalytics */, + 594341D62D08611700A6C0B5 /* WireCountly */, ); productName = "ZClient iOS"; productReference = 8F42C538199244A700288E4D /* Wire.app */; @@ -8679,6 +8608,7 @@ E9816C992CC929FC00D77F22 /* WireFoundationSupport */, 59C2F09E2CA54E4900B25E5D /* WireMainNavigationUI */, 59B4046D2CAB141000CC33BF /* WireSettingsUI */, + 59191A642D0051C7001AB388 /* WireLogging */, ); productName = "ZClient-iOS Tests"; productReference = BACB88511AF7C48900DDCDB0 /* Wire-iOS-Tests.xctest */; @@ -8719,9 +8649,13 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 5983A3EE2CF86F9A002006F6 /* Logging */, + ); name = WireCommonComponents; packageProductDependencies = ( 016A141C2CE6BFC4006A7EF5 /* WireDatadog */, + 59537D8E2CFFA05600920B59 /* WireLogging */, ); productName = WireCommonComponents; productReference = F1FEA14A21DCEB1700790A54 /* WireCommonComponents.framework */; @@ -8817,7 +8751,6 @@ 061F34172B1E2E8F0099CBAB /* XCRemoteSwiftPackageReference "AppAuth-iOS" */, E9FBF3A42C47C23100C65DA8 /* XCRemoteSwiftPackageReference "FLAnimatedImage" */, CB4E15102C81CC81005DDEC8 /* XCRemoteSwiftPackageReference "Down" */, - E92C8CC42C942A5C00E0F7F7 /* XCRemoteSwiftPackageReference "Inject" */, ); productRefGroup = 8F42C539199244A700288E4D /* Products */; projectDirPath = ""; @@ -9342,7 +9275,6 @@ BF8EE5561D62FBCD00B0D6D7 /* ConversationInputBarViewController+Editing.swift in Sources */, 16A94AEF2099DFEC008A5718 /* CallParticipantsListViewController.swift in Sources */, 160D691C212AC94100096EAD /* ImageResource.swift in Sources */, - 54F7C27F1D74367E004D8087 /* AddressBookHelper.swift in Sources */, BFAAB2431DEDB21E00CBC096 /* ConversationListViewController+Transitions.swift in Sources */, 8742C2F31E534ED500329FB6 /* SearchResultLabel.swift in Sources */, 5EEFC16820BEBE56007E8A79 /* BrowserViewController.swift in Sources */, @@ -9353,7 +9285,6 @@ 871BC2871D34F8F800DF0793 /* UIFont+MonoSpaced.swift in Sources */, EF298BD1228D942A0078BE68 /* ImageManagerProtocol.swift in Sources */, A9C33E0023D078B80069C8BB /* ConversationListItemView.swift in Sources */, - A9730DDA250274150061BF36 /* CountlyInstance.swift in Sources */, 2577CB822B1DFF66007D3443 /* ViewConstants.swift in Sources */, 5E8FFC1A21EF6EB10052DF03 /* RemoveClientStepViewController.swift in Sources */, 87F280AA1D080E8400F813CA /* AVSMediaManager+CustomSounds.swift in Sources */, @@ -9397,7 +9328,6 @@ A9806A3C23E209FC00691DFE /* AudioTrack.swift in Sources */, E9E49A5F2CBED7FB0026801A /* SectionTableFooter.swift in Sources */, EF834B8422035AD400E92F13 /* ConversationContentViewController+MessageAction.swift in Sources */, - 8712134B1FA37882008B6767 /* TrackingManager.swift in Sources */, F1199D371FC8490A0070FAC3 /* AuthenticationStepController.swift in Sources */, D53DB9A5203EE03400655CB4 /* Conversation+OptionsConfiguration.swift in Sources */, F15650DC21DD10A100210504 /* AvatarImageView.swift in Sources */, @@ -9421,7 +9351,6 @@ 161B75C820B6BCB50002B0F7 /* ZMUserSession+Calling.swift in Sources */, EFF9404822414E03004F3115 /* ZClientViewController+ProfileViewControllerDelegate.swift in Sources */, 25B236922B67A97E00EDB479 /* ClientTableViewCellModel.swift in Sources */, - EF0154B7225B922200A77FB6 /* ContactsViewController+Constraints.swift in Sources */, 871BC2811D34F8F800DF0793 /* String+Fingerprint.swift in Sources */, D3DA6E7B292F67680045CC57 /* CallingActionsView.swift in Sources */, 596184AD2CC7B5F700787AF0 /* DefaultSettingsPropertyFactoryDelegate.swift in Sources */, @@ -9480,7 +9409,6 @@ F15650F321DD1B6D00210504 /* UserType+ProfileImage.swift in Sources */, 5E8DA74D211AEB9E00360979 /* AuthenticationEventHandler.swift in Sources */, 5E53534421B14A8A0060DB38 /* MessageDetailsCellDescription.swift in Sources */, - EF2A8DEB214A9AFD002C9058 /* ContactsCell.swift in Sources */, E902B3B429ED3A240090DE32 /* SwiftUIButton+HapticFeedback.swift in Sources */, E9E49A5D2CBED6FD0026801A /* SectionFooterView.swift in Sources */, 0667FE312384237E00F75F93 /* UICollectionView+SetMessage.swift in Sources */, @@ -9513,7 +9441,6 @@ EF3BA5D421075FC60093048F /* ConversationInputBarViewController+Language.swift in Sources */, E9C0B3652B5594A500BC80E8 /* ConversationMissedCallSystemMessageCellDescription.swift in Sources */, 87D679E61EBC8002005BEB4C /* String+Compatibility.swift in Sources */, - 590BD7B82C00BD1C0093435A /* ShareContactsViewControllerDelegate.swift in Sources */, 5952693F2BE8C93B001C1E8B /* AppLockView.swift in Sources */, 8719FD671C88802C005FBEC0 /* Delay.swift in Sources */, A955D3D323F54AC400304851 /* AuthenticationCoordinatedViewController.swift in Sources */, @@ -9573,7 +9500,6 @@ 5E678061204E8BEB002F63FB /* CallQualityPresentationTransition.swift in Sources */, EE01D08625ADA3D5001DB205 /* AppLockModule.Presenter.swift in Sources */, D5168F282008ED0700F8222A /* KeyboardBlockObserver.swift in Sources */, - EF7E96EE214BB41800475C63 /* ContactsViewController+ContactsDataSourceDelegate.swift in Sources */, BF0BDF6D1DA6794E003C61DE /* ConversationInputBarViewController+Ephemeral.swift in Sources */, 8751A6CB1FF6573D00804A58 /* QuickActionsManager.swift in Sources */, 63238C6D2B860E4600951467 /* DeveloperE2eiViewModel.swift in Sources */, @@ -9599,7 +9525,6 @@ 54B27986258113FD0043ED25 /* AuthenticatedRouter.swift in Sources */, EF1A461821AD5A270009B6B9 /* GroupDetailsReceiptOptionsCell.swift in Sources */, 87EB6B871E2FCE080019B3C5 /* ZMConversationMessage+File.swift in Sources */, - 590A5F072BF4CB1B008E87D8 /* PermissionDeniedViewController+addressBook.swift in Sources */, 87F18BBA1E01551600C69D9B /* VideoMessageView.swift in Sources */, 5EE73BEB212307D10032986D /* RegistrationActivationExistingAccountPolicyHandler.swift in Sources */, 63421E4B2614C25700ADA723 /* NotificationLabel.swift in Sources */, @@ -9772,8 +9697,6 @@ D56A2D44207DFC9E00DB59F5 /* BackupRestoreController+Failed.swift in Sources */, D51894792050490E00C095C1 /* ActionCell.swift in Sources */, D39210A12A1C9C8700FA616A /* ReactionSectionViewController.swift in Sources */, - 87DCF4941D34E9BA00BB420F /* ZMAddressBookContact+LocalInvitation.swift in Sources */, - EF7E96F0214BFBE100475C63 /* ContactsViewController+UITableViewDelegate.swift in Sources */, 59A27BB02C2ACC8F00195393 /* TopOverlayPresenting.swift in Sources */, 87AAE4231E43234A00E1D13A /* MessagePresenter+ConversationImages.swift in Sources */, 5E65A7A5213048AB008BFCC0 /* UserEmailUpdateFailureErrorHandler.swift in Sources */, @@ -9819,7 +9742,6 @@ 6328284324A11847000748D2 /* UserTypeIconStyle.swift in Sources */, 5E35F78421833AF000D3F4FE /* ZMConversationMessage+Sender.swift in Sources */, 1648EEED20498E1300CC6B37 /* SearchSectionController.swift in Sources */, - E996C35C2CA2FA05008924A4 /* TrackingManagerError.swift in Sources */, 879E5E991F0104A6007A88A3 /* UITextView+Autocorrect.swift in Sources */, 877798AC205A8EF500898A5E /* ZClientViewController.swift in Sources */, BF5FAC0120B6B738004CEB45 /* UIAlertController+OngoingCall.swift in Sources */, @@ -9830,7 +9752,6 @@ EE593F9A2B73C3E6008D1109 /* DeepLinksViewModel.swift in Sources */, EF6E9FEE231D4426000B7785 /* ConversationListViewControllerViewModel+ZMConversationListObserver.swift in Sources */, 162917C91F8BB991000F3E92 /* NetworkStatusView.swift in Sources */, - EF3BD6A0225CD4FE00ABB9B6 /* ShareContactsViewController.swift in Sources */, 5E936E8F20B590530060E6AA /* UIDevice+Platform.swift in Sources */, EF1A461A21AD60510009B6B9 /* ReceiptOptionsSectionController.swift in Sources */, 63497734268CBDAE00824A05 /* CallGridAction.swift in Sources */, @@ -9875,7 +9796,6 @@ E9F2D6442B55DBEC00133C59 /* ConversationEncryptionInfoSystemMessageCellDescription.swift in Sources */, 6363EC9B23D7622F00E74F67 /* NSMutableAttributedString+CompanyLogin.swift in Sources */, EFDB3135217F5E0300D2A7CE /* ConversationListCell.swift in Sources */, - EEDEA26823ED609C000EF7E3 /* ContactsViewController+Invite.swift in Sources */, EE34D6D924336A6600F5BD1C /* TeamMetadataRefresher.swift in Sources */, A949418723E08AF6001B0373 /* Settings.swift in Sources */, 7C25D755214911AC002E22BF /* UserSearchResultsViewController.swift in Sources */, @@ -9912,7 +9832,6 @@ F18993B62241356C00535A74 /* ConversationContentViewController+Scrolling.swift in Sources */, BFBDDBC61DB0D51C00BEED02 /* ConversationInputBarButtonState.swift in Sources */, EE6C2127284118D80031EFB9 /* ConversationCreateEncryptionProtocolCell.swift in Sources */, - E9DA6FF32C5CCBC600BF8298 /* TrackingManager+UIAlertController.swift in Sources */, 5E39FC6B225F81FD00C682B8 /* ConversationViewController+GuestBar.swift in Sources */, EE0DE1CD21B172BF00CF3C4E /* ConversationCreateGuestsSectionController.swift in Sources */, E57917B128999A240063339D /* UIView+Borders.swift in Sources */, @@ -10002,14 +9921,12 @@ 5EA73D8A20D901000001D106 /* CheckmarkCell.swift in Sources */, E66D4E822BE525DF00C7F374 /* AVSVideoContainerView.swift in Sources */, F15650D821DD107600210504 /* ContinuousMaskLayer.swift in Sources */, - EFDE37FD213994750095DCA4 /* ContactsSectionHeaderView.swift in Sources */, EE2538852166151C00932606 /* ConversationActionController+Notifications.swift in Sources */, 5E35F7782181D51300D3F4FE /* ConversationIconBasedCell.swift in Sources */, 87A540A81E09450B009028C5 /* CollectionViewLeftAlignedFlowLayout.swift in Sources */, BF0515251E390E10000D5C22 /* CopyableLabel.swift in Sources */, E966C50D2CDCD43200BF1195 /* FolderPickerBuilder.swift in Sources */, 875E7D1B2188C25A00E571B7 /* ReplyRoundCornersView.swift in Sources */, - EF298BD3228D9BB20078BE68 /* StartUIViewController+AddressBook.swift in Sources */, D53D390E20596ED300B87DAD /* ConversationActionController+Block.swift in Sources */, A97BC011241EA2660069E145 /* NSAttributedString+EnumerateAttachment.swift in Sources */, 63F65EFD2469967600534A69 /* AuthenticationCoordinator+PreBackendSwitch.swift in Sources */, @@ -10044,7 +9961,6 @@ 875060731E5B3D9E005B21C7 /* FileBackupExcluder.swift in Sources */, 595C49362CA93D7200F8F881 /* AnyConversationListCoordinator.swift in Sources */, 5EA73D8820D901000001D106 /* DetailsCollectionViewCell.swift in Sources */, - A9EB722323C1581A00DA8240 /* ContactsViewController.swift in Sources */, EF98987620C6AAD2005680C5 /* ClientColorVariantProtocol.swift in Sources */, 16519D8B232108EF00C9D76D /* ConversationActionController+DeleteGroup.swift in Sources */, 06ADEA062BD22D40008BA0B3 /* RemoveClientsViewModel.swift in Sources */, @@ -10095,7 +10011,6 @@ A9A6506424D1C2C800B6A68D /* PasscodeError.swift in Sources */, F1B5EAC72029AB9A0081D402 /* SimpleTextField.swift in Sources */, A9BA580D2497994D005B806B /* ContextMenuDelegate.swift in Sources */, - EE948F1923E17778000A663E /* ContactsDataSource.swift in Sources */, 060C06652B73DFC700B484C6 /* E2EINotificationActionsHandler.swift in Sources */, E9C0B3612B55933600BC80E8 /* ConversationParticipantsChangedSystemMessageCellDescription.swift in Sources */, 16E2A0A61F6BF233005F4DB1 /* ZMConversation+Calling.swift in Sources */, @@ -10117,7 +10032,6 @@ A937A5C323D5F0400080A507 /* ConversationInputBarViewControllerDelegate.swift in Sources */, BFAC71FE20A1CE0D002711C4 /* CallGridConfiguration.swift in Sources */, D3A74DBD29432E8B00DD272F /* EstablishingCallStatusView.swift in Sources */, - EFDC36DC2257ACE800F916F3 /* StartUIInviteActionBar.swift in Sources */, 637387CD26458EDB004FEF79 /* ScalableView.swift in Sources */, 87A540AA1E094BA3009028C5 /* CollectionCell.swift in Sources */, A9D19B182385245E00F6E14A /* ProfileViewControllerViewModel.swift in Sources */, @@ -10193,6 +10107,7 @@ 5E65A7A721304B6B008BFCC0 /* UserEmailUpdateCodeSentEventHandler.swift in Sources */, EF3371C22216D9D9005ED048 /* ZMUser+Self.swift in Sources */, EFEE97C823229CF5007A4702 /* ConversationListCellDelegate.swift in Sources */, + CB8E51E32CF89D9B00F7F01D /* ConversationFilterSelector.swift in Sources */, 7CED30721FD97748009F0DAC /* IconStringsBuilder.swift in Sources */, E9B0DED42B5E7151006DC9E4 /* AccentColorPicker.swift in Sources */, EFABC92420208D80001F9866 /* UIViewController+removeUserConfirmationUI.swift in Sources */, @@ -10256,6 +10171,7 @@ 1639A85D2265FC8800868AB9 /* UIAlertController+Availability.swift in Sources */, 16D74BEE2B5933D000160298 /* AddUsernameStepDescription.swift in Sources */, BFE57B4B1D52335A00CB0806 /* ConversationPreviewViewController.swift in Sources */, + 01D2C78F2CECC41D00F05E5E /* QRCodeScannerViewController.swift in Sources */, 5E628021221ED0A60039A8AB /* ProfileActionsFactory.swift in Sources */, EF2D6D43228C741500D8DBF4 /* PhotoPermissionsControllerStrategy.swift in Sources */, 5EBF4A2B21B958F30021CFFC /* MessageToolboxDataSource.swift in Sources */, @@ -10313,7 +10229,6 @@ A904740F23CF645F00A4453A /* MediaBarViewController.swift in Sources */, A9CBBEC524CF81EE006543D4 /* UIView+ConstraintFactory.swift in Sources */, 166390D41DC34CAB00D7BF1C /* ImageToolbarView.swift in Sources */, - EF6DAD6A2150F2C400D9BA70 /* ContactsViewController+SearchHeaderViewControllerDelegate.swift in Sources */, E9DD582F2C539D0C00645A2F /* ChangeEmailError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -10377,13 +10292,11 @@ E9D141652AC71719005EE75D /* CreateSecureGuestLinkViewModelTests.swift in Sources */, 16D4932C203DD480008DDFFA /* UserCellTests.swift in Sources */, 5E7315382191FCCB00255A78 /* ConversationMessageActionControllerTests.swift in Sources */, - 1E98C81B1BD1719500C7A7A2 /* ContactsDataSourceTests.swift in Sources */, 162E8C631CF5D68A004B4F45 /* AudioProcessingTests.swift in Sources */, EFE6151C2260E4AA006D48ED /* MultiParagraphSnapshotTests.swift in Sources */, EE7F466925B1CD60001B3EE4 /* AppLockModule.MockSession.swift in Sources */, D337012828996269009C86FD /* ConversationInputBarViewController+DropInteractionTests.swift in Sources */, F1C9E3601EAE008C006438D7 /* TestSetup.swift in Sources */, - EFE0439B2149D9CA00BC3079 /* ShareContactsViewControllerSnapshotTests.swift in Sources */, 0679DEE12C49729500044FC7 /* URLsTests.swift in Sources */, BFC9149B1D181BE7001F068B /* LocationMessageCellTests.swift in Sources */, E9398D332B5FD58E00C4AFBA /* ConversationSystemMessageMlsSupportSnapshotTests.swift in Sources */, @@ -10475,7 +10388,6 @@ E6F442FD2B15FD3200D2B08A /* ConversationSystemMessageCellSnapshotTests.swift in Sources */, 6372A0F225C8438B000CCB2D /* SelfCallParticipantViewTests.swift in Sources */, 5E8FFC0621ECCC7C0052DF03 /* AuthenticationInterfaceBuilderTests.swift in Sources */, - EFE043992149CE8D00BC3079 /* ContactsViewControllerSnapshotTests.swift in Sources */, EF4C5D4A23351CF00092CA38 /* ConversationListViewModelTests.swift in Sources */, EF68856820F611D50074CA0F /* CanvasViewControllerTests.swift in Sources */, EF31DC7C2092020300A4F09F /* UIViewController+iPhoneSize.swift in Sources */, @@ -10557,7 +10469,6 @@ A93E6104233B870300CEA65F /* ConversationListHeaderViewSnapshotTests.swift in Sources */, A95C76C2240E915D0047CD29 /* CompositeMessageCellTests.swift in Sources */, EF223F5D2327A1CF004480FA /* ConversationListViewControllerViewModelTests.swift in Sources */, - 01C488B02C8A45F80066789E /* WireAnalytics_DatadogTests.swift in Sources */, 63C4B3BD2C359D9900C09A93 /* SettingsDebugReportViewControllerSnapshotTests.swift in Sources */, E9B439312B0783BD0069DA63 /* SelfProvider.swift in Sources */, EF2FEDC8228E9E42000883C8 /* UserImageViewContainerSnapshotTests.swift in Sources */, @@ -10646,7 +10557,6 @@ E961C0752A332E7400A36DE6 /* ConversationReactionMessageTests.swift in Sources */, 6370DA212577934C00B5C8F1 /* PulsingIconImageViewTests.swift in Sources */, BF78687F1D86B3710063762F /* LocationDataTests.swift in Sources */, - EF2A8DEF214AAB13002C9058 /* ContactsCellSnapshotTests.swift in Sources */, D32D8D2F2A1364CC00482788 /* MessageActionsViewControllerTests.swift in Sources */, EF25F8871FC57F320040C3CC /* ValidatedTextFieldTests.swift in Sources */, BF2A74751CEB595E002608BF /* AudioButtonOverlayTests.swift in Sources */, @@ -11296,11 +11206,6 @@ ); LIBRARY_SEARCH_PATHS = "$(inherited)"; ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = ( - "-ObjC", - "-Xlinker", - "-interposable", - ); SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -11687,14 +11592,6 @@ version = 2.3.5; }; }; - E92C8CC42C942A5C00E0F7F7 /* XCRemoteSwiftPackageReference "Inject" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/krzysztofzablocki/Inject"; - requirement = { - kind = exactVersion; - version = 1.5.2; - }; - }; E9FBF3A42C47C23100C65DA8 /* XCRemoteSwiftPackageReference "FLAnimatedImage" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Flipboard/FLAnimatedImage"; @@ -11746,6 +11643,10 @@ isa = XCSwiftPackageProductDependency; productName = WireFoundation; }; + 59191A642D0051C7001AB388 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; 5929CB212CA6C9C800070488 /* WireConversationListUI */ = { isa = XCSwiftPackageProductDependency; productName = WireConversationListUI; @@ -11754,10 +11655,34 @@ isa = XCSwiftPackageProductDependency; productName = WireReusableUIComponents; }; + 594341D62D08611700A6C0B5 /* WireCountly */ = { + isa = XCSwiftPackageProductDependency; + productName = WireCountly; + }; 5945D0292C219F7200D039E3 /* WireDesign */ = { isa = XCSwiftPackageProductDependency; productName = WireDesign; }; + 594906A42D0713C000238104 /* WireAnalytics */ = { + isa = XCSwiftPackageProductDependency; + productName = WireAnalytics; + }; + 59537D8E2CFFA05600920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; + 59537D902CFFA0BA00920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; + 59537D922CFFA0DA00920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; + 59537D942CFFA11A00920B59 /* WireLogging */ = { + isa = XCSwiftPackageProductDependency; + productName = WireLogging; + }; 595C49682CA995E900F8F881 /* WireSettingsUI */ = { isa = XCSwiftPackageProductDependency; productName = WireSettingsUI; @@ -11814,6 +11739,10 @@ isa = XCSwiftPackageProductDependency; productName = WireMainNavigationUI; }; + 76D15E702CEFAE8A0059215D /* WireIndividualToTeamMigrationUI */ = { + isa = XCSwiftPackageProductDependency; + productName = WireIndividualToTeamMigrationUI; + }; CB43D9BC2CDBBBB900BF5AEB /* WireFolderPickerUI */ = { isa = XCSwiftPackageProductDependency; productName = WireFolderPickerUI; @@ -11823,11 +11752,6 @@ package = CB4E15102C81CC81005DDEC8 /* XCRemoteSwiftPackageReference "Down" */; productName = Down; }; - E92C8CC52C942A5C00E0F7F7 /* Inject */ = { - isa = XCSwiftPackageProductDependency; - package = E92C8CC42C942A5C00E0F7F7 /* XCRemoteSwiftPackageReference "Inject" */; - productName = Inject; - }; E9816C992CC929FC00D77F22 /* WireFoundationSupport */ = { isa = XCSwiftPackageProductDependency; productName = WireFoundationSupport; diff --git a/wire-ios/Wire-iOS.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/wire-ios/Wire-iOS.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index f9b0d7c5ea1..ff23ebc8177 100644 --- a/wire-ios/Wire-iOS.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/wire-ios/Wire-iOS.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -2,6 +2,8 @@ + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + PreviewsEnabled diff --git a/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/Format & Lint Swift Code.xcscheme b/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/Format & Lint Swift Code.xcscheme index 49476215ac9..6d1e12107d6 100644 --- a/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/Format & Lint Swift Code.xcscheme +++ b/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/Format & Lint Swift Code.xcscheme @@ -1,6 +1,6 @@ + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> @@ -41,9 +41,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> diff --git a/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/Wire Share Extension.xcscheme b/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/Wire Share Extension.xcscheme index b3e7c444895..92f7659d600 100644 --- a/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/Wire Share Extension.xcscheme +++ b/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/Wire Share Extension.xcscheme @@ -1,11 +1,11 @@ + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> - - - + - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/WireCommonComponents.xcscheme b/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/WireCommonComponents.xcscheme index cd9760db716..966f592d108 100644 --- a/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/WireCommonComponents.xcscheme +++ b/wire-ios/Wire-iOS.xcodeproj/xcshareddata/xcschemes/WireCommonComponents.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1610" + version = "1.7"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> - - - - String { - return L10n.tr("Localizable", "conversation.create.options.subtitle", String(describing: p1), String(describing: p2), String(describing: p3), fallback: "Guests: %@, Services: %@, Read receipts: %@") - } /// CONVERSATION OPTIONS internal static let title = L10n.tr("Localizable", "conversation.create.options.title", fallback: "CONVERSATION OPTIONS") } @@ -4453,14 +4449,10 @@ internal enum L10n { internal static let hideSearchResult = L10n.tr("Localizable", "peoplepicker.hide_search_result", fallback: "Hide") /// Hiding… internal static let hideSearchResultProgress = L10n.tr("Localizable", "peoplepicker.hide_search_result_progress", fallback: "Hiding…") - /// Invite more people - internal static let inviteMorePeople = L10n.tr("Localizable", "peoplepicker.invite_more_people", fallback: "Invite more people") /// Invite people to join the team internal static let inviteTeamMembers = L10n.tr("Localizable", "peoplepicker.invite_team_members", fallback: "Invite people to join the team") /// No Contacts. internal static let noContactsTitle = L10n.tr("Localizable", "peoplepicker.no_contacts_title", fallback: "No Contacts.") - /// No results. - internal static let noMatchingResultsAfterAddressBookUploadTitle = L10n.tr("Localizable", "peoplepicker.no_matching_results_after_address_book_upload_title", fallback: "No results.") /// No matching results. Try entering a different name. internal static let noSearchResults = L10n.tr("Localizable", "peoplepicker.no_search_results", fallback: "No matching results. Try entering a different name.") /// Search by name or username @@ -4827,24 +4819,6 @@ internal enum L10n { internal static let title = L10n.tr("Localizable", "registration.add_email_password.hero.title", fallback: "Add your email and password") } } - internal enum AddressBookAccessDenied { - internal enum Hero { - /// Wire helps find your friends if you share your contacts. - internal static let paragraph1 = L10n.tr("Localizable", "registration.address_book_access_denied.hero.paragraph1", fallback: "Wire helps find your friends if you share your contacts.") - /// To enable access tap Settings and turn on Contacts. - internal static let paragraph2 = L10n.tr("Localizable", "registration.address_book_access_denied.hero.paragraph2", fallback: "To enable access tap Settings and turn on Contacts.") - /// Wire does not have access to your contacts. - internal static let title = L10n.tr("Localizable", "registration.address_book_access_denied.hero.title", fallback: "Wire does not have access to your contacts.") - } - internal enum MaybeLaterButton { - /// Maybe later - internal static let title = L10n.tr("Localizable", "registration.address_book_access_denied.maybe_later_button.title", fallback: "Maybe later") - } - internal enum SettingsButton { - /// Settings - internal static let title = L10n.tr("Localizable", "registration.address_book_access_denied.settings_button.title", fallback: "Settings") - } - } internal enum Alert { /// Register with Another Email internal static let changeEmailAction = L10n.tr("Localizable", "registration.alert.change_email_action", fallback: "Register with Another Email") @@ -5048,22 +5022,6 @@ internal enum L10n { internal static let subtitleLink = L10n.tr("Localizable", "registration.select_handle.takeover.subtitle_link", fallback: "Learn more") } } - internal enum ShareContacts { - internal enum FindFriendsButton { - /// Share contacts - internal static let title = L10n.tr("Localizable", "registration.share_contacts.find_friends_button.title", fallback: "Share contacts") - } - internal enum Hero { - /// Share your contacts so we can connect you with others. We anonymize all information and do not share it with anyone else. - internal static let paragraph = L10n.tr("Localizable", "registration.share_contacts.hero.paragraph", fallback: "Share your contacts so we can connect you with others. We anonymize all information and do not share it with anyone else.") - /// Find people on Wire - internal static let title = L10n.tr("Localizable", "registration.share_contacts.hero.title", fallback: "Find people on Wire") - } - internal enum SkipButton { - /// Not now - internal static let title = L10n.tr("Localizable", "registration.share_contacts.skip_button.title", fallback: "Not now") - } - } internal enum Signin { /// Log in internal static let title = L10n.tr("Localizable", "registration.signin.title", fallback: "Log in") diff --git a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings index 006f2389a61..4d3b5bb371c 100644 --- a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings @@ -86,7 +86,6 @@ "peoplepicker.no_matching_results.action.learn_more" = "Learn more"; "peoplepicker.no_matching_results.action.manage_services" = "Manage Services"; -"peoplepicker.no_matching_results_after_address_book_upload_title" = "No results."; "peoplepicker.no_matching_results.message.users" = "Find people in Wire by name or @username"; "peoplepicker.no_matching_results.message.usersAndFederation" = "Find people in Wire by name or @username\n\nFind people on another domain by @username@domainname"; "peoplepicker.no_matching_results.message.users_all_added" = "Everyone’s here."; @@ -98,7 +97,6 @@ "peoplepicker.send_invitation.dialog.message" = "It can be used for 2 weeks. Send a new one if it expires."; "peoplepicker.send_invitation.dialog.ok" = "OK"; -"peoplepicker.invite_more_people" = "Invite more people"; "peoplepicker.invite_team_members" = "Invite people to join the team"; "peoplepicker.quick-action.open-conversation" = "Open"; "peoplepicker.quick-action.admin-services" = "Manage Services"; @@ -334,7 +332,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; @@ -1591,17 +1588,6 @@ "registration.country_select.title" = "Country"; -"registration.share_contacts.hero.title" = "Find people on Wire"; -"registration.share_contacts.hero.paragraph" = "Share your contacts so we can connect you with others. We anonymize all information and do not share it with anyone else."; -"registration.share_contacts.find_friends_button.title" = "Share contacts"; -"registration.share_contacts.skip_button.title" = "Not now"; - -"registration.address_book_access_denied.hero.title" = "Wire does not have access to your contacts."; -"registration.address_book_access_denied.hero.paragraph1" = "Wire helps find your friends if you share your contacts."; -"registration.address_book_access_denied.hero.paragraph2" = "To enable access tap Settings and turn on Contacts."; -"registration.address_book_access_denied.settings_button.title" = "Settings"; -"registration.address_book_access_denied.maybe_later_button.title" = "Maybe later"; - "registration.push_access_denied.hero.title" = "Never miss a call or a message."; "registration.push_access_denied.hero.paragraph1" = "Enable Notifications in Settings."; "registration.push_access_denied.settings_button.title" = "Go to Settings"; diff --git a/wire-ios/Wire-iOS/Resources/ar.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/ar.lproj/Localizable.strings index 800ad948f5e..6113eb5b94e 100644 --- a/wire-ios/Wire-iOS/Resources/ar.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/ar.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "على الأقل حرف واحد"; "conversation.create.guidance.toolong" = "حروف كثيرة عن الحد"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "اسمح بالضيوف"; "conversation.create.guests.subtitle" = "إتاحة هذه المحادثة لأشخاص خارج فريقك."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/be.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/be.lproj/Localizable.strings index 0cc87ecda1c..3ddcb3acc3c 100644 --- a/wire-ios/Wire-iOS/Resources/be.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/be.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/bg.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/bg.lproj/Localizable.strings index a2caf54a02d..07843baa2d1 100644 --- a/wire-ios/Wire-iOS/Resources/bg.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/bg.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/bn.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/bn.lproj/Localizable.strings index 312a7521747..a3745c57f06 100644 --- a/wire-ios/Wire-iOS/Resources/bn.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/bn.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/ca.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/ca.lproj/Localizable.strings index 882c8a91376..978ef7c9ddc 100644 --- a/wire-ios/Wire-iOS/Resources/ca.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/ca.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Permet convidats"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/cs.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/cs.lproj/Localizable.strings index e59d9f2b31e..d24fae689f5 100644 --- a/wire-ios/Wire-iOS/Resources/cs.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/cs.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Alespoň jeden znak"; "conversation.create.guidance.toolong" = "Příliš mnoho znaků"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Povolit hosty"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/da.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/da.lproj/Localizable.strings index 445b6408ed9..1d2cfab3f93 100644 --- a/wire-ios/Wire-iOS/Resources/da.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/da.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Mindst 1 tegn"; "conversation.create.guidance.toolong" = "For mange tegn"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Tillad gæster"; "conversation.create.guests.subtitle" = "Åbn denne samtale for personer uden for dit team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/de.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/de.lproj/Localizable.strings index fe87aa62d70..030201fb15a 100644 --- a/wire-ios/Wire-iOS/Resources/de.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/de.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Mindestens 1 Zeichen"; "conversation.create.guidance.toolong" = "Zu viele Zeichen"; "conversation.create.options.title" = "UNTERHALTUNGSOPTIONEN"; -"conversation.create.options.subtitle" = "Gäste: %@, Dienste: %@, Lesebestätigungen: %@"; "conversation.create.guests.title" = "Gäste zulassen"; "conversation.create.guests.subtitle" = "Öffnen Sie diese Unterhaltung für Personen außerhalb Ihres Teams."; "conversation.create.services.title" = "Dienste zulassen"; diff --git a/wire-ios/Wire-iOS/Resources/el.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/el.lproj/Localizable.strings index a1acc9965fc..48cbfc213c4 100644 --- a/wire-ios/Wire-iOS/Resources/el.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/el.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/es.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/es.lproj/Localizable.strings index bd2bcc737fc..eb9d07ccbe7 100644 --- a/wire-ios/Wire-iOS/Resources/es.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/es.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Por lo menos 1 caracter"; "conversation.create.guidance.toolong" = "Demasiados caracteres"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Permitir invitados"; "conversation.create.guests.subtitle" = "Abrir esta conversación a personas fuera de su equipo."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/et.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/et.lproj/Localizable.strings index 5391e0a8ff1..684e55b0c54 100644 --- a/wire-ios/Wire-iOS/Resources/et.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/et.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Vähemalt 1 tähemärk"; "conversation.create.guidance.toolong" = "Liiga pikk nimi"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Luba külalisi"; "conversation.create.guests.subtitle" = "Ava see vestlus meeskonnast väljas olevatele inimestele."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/eu.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/eu.lproj/Localizable.strings index 9fffcfb32dd..4ad5c9a92d9 100644 --- a/wire-ios/Wire-iOS/Resources/eu.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/eu.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/fa.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/fa.lproj/Localizable.strings index 00fa2df01f3..e9cc999c4f7 100644 --- a/wire-ios/Wire-iOS/Resources/fa.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/fa.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "حداقل یک حرف"; "conversation.create.guidance.toolong" = "تعداد کاراکترها زیاد است"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/fi.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/fi.lproj/Localizable.strings index ebb4cfd2258..0aecf8dc134 100644 --- a/wire-ios/Wire-iOS/Resources/fi.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/fi.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Liian monta merkkiä"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/fr.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/fr.lproj/Localizable.strings index 548edc6a352..64f1fcf5526 100644 --- a/wire-ios/Wire-iOS/Resources/fr.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/fr.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Au moins un caractère"; "conversation.create.guidance.toolong" = "Trop de caractères"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Invités : %@, Services : %@, Accusés de lecture : %@"; "conversation.create.guests.title" = "Autoriser les invités"; "conversation.create.guests.subtitle" = "Rendre cette conversation accessible à des personnes externes à votre équipe."; "conversation.create.services.title" = "Autoriser les services"; diff --git a/wire-ios/Wire-iOS/Resources/he.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/he.lproj/Localizable.strings index 1b1bb90f478..246b0f6eb68 100644 --- a/wire-ios/Wire-iOS/Resources/he.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/he.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/hr.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/hr.lproj/Localizable.strings index 36036814296..8521651d181 100644 --- a/wire-ios/Wire-iOS/Resources/hr.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/hr.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Najmanje 1 znak"; "conversation.create.guidance.toolong" = "Prevelik broj znakova"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Dopustite goste"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/hu.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/hu.lproj/Localizable.strings index b99a2979a4a..bacc150d925 100644 --- a/wire-ios/Wire-iOS/Resources/hu.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/hu.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Legalább 1 karakter"; "conversation.create.guidance.toolong" = "Túl sok karakter"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Vendégek engedélyezése"; "conversation.create.guests.subtitle" = "Megnyitja a beszélgetést a csapatodon kívüli partnerek számára is."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/id.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/id.lproj/Localizable.strings index e435294e102..c2e21ab5dd3 100644 --- a/wire-ios/Wire-iOS/Resources/id.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/id.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Terlalu banyak karakter"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/is.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/is.lproj/Localizable.strings index 84b3ca9fe5c..2d1d1e7a7fc 100644 --- a/wire-ios/Wire-iOS/Resources/is.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/is.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/it.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/it.lproj/Localizable.strings index 133f774f25c..f5ca877dee8 100644 --- a/wire-ios/Wire-iOS/Resources/it.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/it.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Almeno 1 carattere"; "conversation.create.guidance.toolong" = "Troppi caratteri"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Consenti ospiti"; "conversation.create.guests.subtitle" = "Apri questa conversazione a persone esterne al tuo team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/ja.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/ja.lproj/Localizable.strings index 483a60e477f..94a8eca44b8 100644 --- a/wire-ios/Wire-iOS/Resources/ja.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/ja.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "1文字以上"; "conversation.create.guidance.toolong" = "文字が多すぎます"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "ゲストアクセスを許可"; "conversation.create.guests.subtitle" = "チーム外の人々にこの会話を公開します。"; "conversation.create.services.title" = "サービスを許可"; diff --git a/wire-ios/Wire-iOS/Resources/ka.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/ka.lproj/Localizable.strings index ce983dc3910..6de587dbbc6 100644 --- a/wire-ios/Wire-iOS/Resources/ka.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/ka.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/ko.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/ko.lproj/Localizable.strings index 8b0c1ef3422..4e95e0823ee 100644 --- a/wire-ios/Wire-iOS/Resources/ko.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/ko.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/lt.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/lt.lproj/Localizable.strings index 29dc06efc11..e88ed0d7562 100644 --- a/wire-ios/Wire-iOS/Resources/lt.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/lt.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Bent 1 simbolis"; "conversation.create.guidance.toolong" = "Per daug simbolių"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Leisti svečius"; "conversation.create.guests.subtitle" = "Leiskite matyti šį susirašinėjimą žmonėms nesantiems komandoje."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/lv.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/lv.lproj/Localizable.strings index 1968b69eb66..b0de130db6b 100644 --- a/wire-ios/Wire-iOS/Resources/lv.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/lv.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/ms.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/ms.lproj/Localizable.strings index 4fa9418a1c7..2961290b08d 100644 --- a/wire-ios/Wire-iOS/Resources/ms.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/ms.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/my.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/my.lproj/Localizable.strings index ce983dc3910..6de587dbbc6 100644 --- a/wire-ios/Wire-iOS/Resources/my.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/my.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/nl.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/nl.lproj/Localizable.strings index 7ca16b7b0af..44017e2f11f 100644 --- a/wire-ios/Wire-iOS/Resources/nl.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/nl.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Minimaal 1 teken"; "conversation.create.guidance.toolong" = "Te veel tekens"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Sta gasten toe"; "conversation.create.guests.subtitle" = "Open dit gesprek voor mensen buiten jouw team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/no.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/no.lproj/Localizable.strings index 4394aae7689..912d9131e2c 100644 --- a/wire-ios/Wire-iOS/Resources/no.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/no.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Minst 1 tegn"; "conversation.create.guidance.toolong" = "For mange tegn"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/pl.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/pl.lproj/Localizable.strings index 15ade518a7b..9fc3403ea8b 100644 --- a/wire-ios/Wire-iOS/Resources/pl.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/pl.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Co najmniej 1 znak"; "conversation.create.guidance.toolong" = "Zbyt wiele znaków"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Zezwalaj na gości"; "conversation.create.guests.subtitle" = "Wyraź zgodę na dołączenie do rozmowy osób spoza twojej drużyny."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/pt-BR.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/pt-BR.lproj/Localizable.strings index 84c6352376f..6cb62c10333 100644 --- a/wire-ios/Wire-iOS/Resources/pt-BR.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/pt-BR.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Pelo menos 1 caractere"; "conversation.create.guidance.toolong" = "Muitos caracteres"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Convidados: %@, Serviços: %@, Confirmação de leitura: %@"; "conversation.create.guests.title" = "Permitir convidados"; "conversation.create.guests.subtitle" = "Abra esta conversa para pessoas de fora da sua equipe."; "conversation.create.services.title" = "Permitir serviços"; diff --git a/wire-ios/Wire-iOS/Resources/pt.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/pt.lproj/Localizable.strings index 6aa399cc126..6247d8380fd 100644 --- a/wire-ios/Wire-iOS/Resources/pt.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/pt.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Permitir convidados"; "conversation.create.guests.subtitle" = "Abrir esta conversa a pessoas fora da sua equipa."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/ro.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/ro.lproj/Localizable.strings index a663f8367b6..223aae3b0a6 100644 --- a/wire-ios/Wire-iOS/Resources/ro.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/ro.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Cel puțin un caracter"; "conversation.create.guidance.toolong" = "Prea multe caractere"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/ru.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/ru.lproj/Localizable.strings index 832d3fec1d2..6de515386ed 100644 --- a/wire-ios/Wire-iOS/Resources/ru.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/ru.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Не менее 1 символа"; "conversation.create.guidance.toolong" = "Слишком много символов"; "conversation.create.options.title" = "НАСТРОЙКИ БЕСЕДЫ"; -"conversation.create.options.subtitle" = "Гости: %@, Сервисы: %@, Отчеты о прочтении: %@"; "conversation.create.guests.title" = "Разрешить гостей"; "conversation.create.guests.subtitle" = "Открыть эту беседу для пользователей не из вашей команды."; "conversation.create.services.title" = "Разрешить сервисы"; diff --git a/wire-ios/Wire-iOS/Resources/si.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/si.lproj/Localizable.strings index 633148e41d2..8504abef4af 100644 --- a/wire-ios/Wire-iOS/Resources/si.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/si.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/sk.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/sk.lproj/Localizable.strings index 1ee680b3e2f..8614302ef8b 100644 --- a/wire-ios/Wire-iOS/Resources/sk.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/sk.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/sl.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/sl.lproj/Localizable.strings index b3a87f77b3a..750b1183734 100644 --- a/wire-ios/Wire-iOS/Resources/sl.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/sl.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Preveč znakov"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/sr-Latn.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/sr-Latn.lproj/Localizable.strings index 0bf8a197d73..80901e6ca8d 100644 --- a/wire-ios/Wire-iOS/Resources/sr-Latn.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/sr-Latn.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/sr.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/sr.lproj/Localizable.strings index bc5b8689031..582896c1059 100644 --- a/wire-ios/Wire-iOS/Resources/sr.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/sr.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Барем 1 знак"; "conversation.create.guidance.toolong" = "Предугачко"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Дозволи гостима"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/sv.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/sv.lproj/Localizable.strings index 0df5f4c3f91..928fef7f6f6 100644 --- a/wire-ios/Wire-iOS/Resources/sv.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/sv.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Minst 1 tecken"; "conversation.create.guidance.toolong" = "För många tecken"; "conversation.create.options.title" = "KONVERSATIONSALTERNATIV"; -"conversation.create.options.subtitle" = "Gäster: %@, Tjänster: %@, Läskvitton: %@"; "conversation.create.guests.title" = "Tillåt gäster"; "conversation.create.guests.subtitle" = "Öppna den här konversationen till personer utanför ditt team."; "conversation.create.services.title" = "Tillåt tjänster"; diff --git a/wire-ios/Wire-iOS/Resources/th.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/th.lproj/Localizable.strings index ce983dc3910..6de587dbbc6 100644 --- a/wire-ios/Wire-iOS/Resources/th.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/th.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/tr.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/tr.lproj/Localizable.strings index 466ca465d0a..a02deeb57e0 100644 --- a/wire-ios/Wire-iOS/Resources/tr.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/tr.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "En az 1 karakter"; "conversation.create.guidance.toolong" = "Çok fazla karakter"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Misafirlere izin ver"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/uk.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/uk.lproj/Localizable.strings index 7909566cd90..0fa7210f649 100644 --- a/wire-ios/Wire-iOS/Resources/uk.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/uk.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Не менше 1 символу"; "conversation.create.guidance.toolong" = "Занадто багато символів"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Гості: %@, Сервіси: %@, Звіти про перегляд: %@"; "conversation.create.guests.title" = "Дозволити присутність гостей"; "conversation.create.guests.subtitle" = "Зробити цю розмову доступною для людей поза межами вашої команди."; "conversation.create.services.title" = "Дозволити сервіси"; diff --git a/wire-ios/Wire-iOS/Resources/ur.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/ur.lproj/Localizable.strings index ce983dc3910..6de587dbbc6 100644 --- a/wire-ios/Wire-iOS/Resources/ur.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/ur.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "At least 1 character"; "conversation.create.guidance.toolong" = "Too many characters"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/vi.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/vi.lproj/Localizable.strings index 72368dde498..1b7e3d38681 100644 --- a/wire-ios/Wire-iOS/Resources/vi.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/vi.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "Ít nhất 1 kí tự"; "conversation.create.guidance.toolong" = "Quá nhiều kí tự"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "Allow guests"; "conversation.create.guests.subtitle" = "Open this conversation to people outside your team."; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/zh-Hans.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/zh-Hans.lproj/Localizable.strings index 291c4569af9..2d9f11fb339 100644 --- a/wire-ios/Wire-iOS/Resources/zh-Hans.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/zh-Hans.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "至少1个字符"; "conversation.create.guidance.toolong" = "超出字符限制"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "允许访客"; "conversation.create.guests.subtitle" = "开放这个对话与团队以外的访客。"; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Resources/zh-Hant.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/zh-Hant.lproj/Localizable.strings index 7a36d699687..59263c93eb9 100644 --- a/wire-ios/Wire-iOS/Resources/zh-Hant.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/zh-Hant.lproj/Localizable.strings @@ -334,7 +334,6 @@ "conversation.create.guidance.empty" = "至少1個字元"; "conversation.create.guidance.toolong" = "字數過多"; "conversation.create.options.title" = "CONVERSATION OPTIONS"; -"conversation.create.options.subtitle" = "Guests: %@, Services: %@, Read receipts: %@"; "conversation.create.guests.title" = "允許訪客"; "conversation.create.guests.subtitle" = "開放這個對話與團隊以外的訪客。"; "conversation.create.services.title" = "Allow services"; diff --git a/wire-ios/Wire-iOS/Sources/Analytics/Events/Analytics+Services.swift b/wire-ios/Wire-iOS/Sources/Analytics/Events/Analytics+Services.swift deleted file mode 100644 index 41fdc6ada17..00000000000 --- a/wire-ios/Wire-iOS/Sources/Analytics/Events/Analytics+Services.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Foundation -import WireDataModel - -// swiftlint:disable:next todo_requires_jira_link -// TODO: move to DM -private extension ZMConversation { - var otherNonServiceParticipants: [UserType] { - let users = Array(localParticipants) - return users.filter { !$0.isServiceUser } - } -} - -struct ServiceAddedEvent: Event { - enum Keys { - static let serviceID = "service_id" - static let conversationSize = "conversation_size" - static let servicesSize = "services_size" - static let methods = "methods" - } - - enum Context: String { - case startUI = "start_ui" - case conversationDetails = "conversation_details" - } - - private let conversationSize, servicesSize: Int - private let serviceIdentifier: String - private let context: Context - - init(service: ServiceUser, conversation: ZMConversation, context: Context) { - self.serviceIdentifier = service.serviceIdentifier ?? "" - self.conversationSize = conversation.otherNonServiceParticipants.count // Without service users - self.servicesSize = conversation.localParticipants.count - conversationSize - self.context = context - } - - var name: String { - "integration.added_service" - } - - var attributes: [AnyHashable: Any]? { - [ - Keys.serviceID: serviceIdentifier, - Keys.conversationSize: conversationSize, - Keys.servicesSize: servicesSize, - Keys.methods: context.rawValue - ] - } -} - -struct ServiceRemovedEvent: Event { - enum Keys { - static let serviceID = "service_id" - } - - private let serviceIdentifier: String - - init(service: ServiceUser) { - self.serviceIdentifier = service.serviceIdentifier ?? "" - } - - var name: String { - "integration.removed_service" - } - - var attributes: [AnyHashable: Any]? { - [Keys.serviceID: serviceIdentifier] - } -} - -extension Analytics { - @objc - func tagDidRemoveService(_ serviceUser: ServiceUser) { - tag(ServiceRemovedEvent(service: serviceUser)) - } -} diff --git a/wire-ios/Wire-iOS/Sources/Analytics/Events/ZMConversation+Analytics.swift b/wire-ios/Wire-iOS/Sources/Analytics/Events/ZMConversation+Analytics.swift deleted file mode 100644 index 6dd3e39aead..00000000000 --- a/wire-ios/Wire-iOS/Sources/Analytics/Events/ZMConversation+Analytics.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Foundation -import WireDataModel - -enum ConversationType: Int { - case oneToOne - case group -} - -extension ConversationType { - var analyticsTypeString: String { - switch self { - case .oneToOne: "one_to_one" - case .group: "group" - } - } - - static func type(_ conversation: ZMConversation) -> ConversationType? { - switch conversation.conversationType { - case .oneOnOne: - .oneToOne - case .group: - .group - default: - nil - } - } -} - -extension ZMConversation { - - var analyticsTypeString: String? { - ConversationType.type(self)?.analyticsTypeString - } - - // swiftlint:disable:next todo_requires_jira_link - // TODO: move to DM - /// Whether the conversation includes at least 1 service user. - var includesServiceUser: Bool { - let participants = Array(localParticipants) - return participants.any { $0.isServiceUser } - } - - var attributesForConversation: [String: Any] { - let participants = sortedActiveParticipants - - let attributes: [String: Any] = [ - "conversation_type": analyticsTypeString ?? "invalid", - "with_service": includesServiceUser ? true : false, - "is_allow_guests": accessMode == ConversationAccessMode.allowGuests ? true : false, - "conversation_size": participants.count.logRound(), - "is_global_ephemeral": hasSyncedMessageDestructionTimeout, - "conversation_services": sortedServiceUsers.count.logRound(), - "conversation_guests_wireless": participants.filter { - $0.isWirelessUser && $0.isGuest(in: self) - }.count.logRound(), - "conversation_guests_pro": participants.filter { - $0.isGuest(in: self) && $0.hasTeam - }.count.logRound() - ] - - return attributes.merging(guestAttributes) { _, new in new } - } - - var guestAttributes: [String: Any] { - - let numGuests = sortedActiveParticipants.filter { - $0.isGuest(in: self) - }.count - - var attributes: [String: Any] = [ - "conversation_guests": numGuests.logRound() - ] - - if let selfUser = SelfUser.provider?.providedSelfUser { - attributes["user_type"] = selfUser.isGuest(in: self) ? "guest" : "user" - } - - return attributes - } -} diff --git a/wire-ios/Wire-iOS/Sources/Analytics/TrackingManager/TrackingManager+UIAlertController.swift b/wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/TrackingManager+UIAlertController.swift similarity index 99% rename from wire-ios/Wire-iOS/Sources/Analytics/TrackingManager/TrackingManager+UIAlertController.swift rename to wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/TrackingManager+UIAlertController.swift index 43988f297c4..0513a44860b 100644 --- a/wire-ios/Wire-iOS/Sources/Analytics/TrackingManager/TrackingManager+UIAlertController.swift +++ b/wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/TrackingManager+UIAlertController.swift @@ -17,6 +17,7 @@ // import UIKit +import WireLogging import WireSystem extension TrackingManager { diff --git a/wire-ios/Wire-iOS/Sources/Analytics/TrackingManager/TrackingManager.swift b/wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/TrackingManager.swift similarity index 99% rename from wire-ios/Wire-iOS/Sources/Analytics/TrackingManager/TrackingManager.swift rename to wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/TrackingManager.swift index 15dd2b13bdf..92324e16882 100644 --- a/wire-ios/Wire-iOS/Sources/Analytics/TrackingManager/TrackingManager.swift +++ b/wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/TrackingManager.swift @@ -19,6 +19,7 @@ import avs import Foundation import WireCommonComponents +import WireLogging import WireSyncEngine final class TrackingManager: NSObject, TrackingInterface { diff --git a/wire-ios/Wire-iOS/Sources/Analytics/TrackingManager/TrackingManagerError.swift b/wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/TrackingManagerError.swift similarity index 100% rename from wire-ios/Wire-iOS/Sources/Analytics/TrackingManager/TrackingManagerError.swift rename to wire-ios/Wire-iOS/Sources/AnalyticsTrackingManager/TrackingManagerError.swift diff --git a/wire-ios/Wire-iOS/Sources/AppDelegate.swift b/wire-ios/Wire-iOS/Sources/AppDelegate.swift index 6198392d46f..9aeafbd4d9a 100644 --- a/wire-ios/Wire-iOS/Sources/AppDelegate.swift +++ b/wire-ios/Wire-iOS/Sources/AppDelegate.swift @@ -22,6 +22,8 @@ import avs import UIKit import WireCommonComponents import WireCoreCrypto +import WireCountly +import WireLogging import WireSyncEngine enum ApplicationLaunchType { @@ -97,8 +99,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { func application( _ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil - ) - -> Bool { + ) -> Bool { guard !application.supportsMultipleScenes else { fatalError("Multiple scenes are currently not supported") @@ -109,13 +110,14 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { } mainWindow = .init(windowScene: windowScene) + setNavigationAppearance() // enable logs _ = Settings.shared // switch logs ZMSLog.switchCurrentLogToPrevious() - // Set up Datadog as logger - WireAnalytics.Datadog.enable() + // Set up Datadog and other loggers + WireAnalytics.setup() WireLogger.appDelegate.info( "application:willFinishLaunchingWithOptions \(String(describing: launchOptions)) (applicationState = \(application.applicationState))" @@ -130,6 +132,12 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { return true } + private func setNavigationAppearance() { + let backIndicator = UIImage(resource: mainWindow.isRightToLeft == true ? .forwardArrow : .backArrow) + UINavigationBar.appearance().backIndicatorImage = backIndicator + UINavigationBar.appearance().backIndicatorTransitionMaskImage = backIndicator + } + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { WireLogger.push.info( "application did register for remote notifications, storing standard token", @@ -380,7 +388,8 @@ private extension AppDelegate { sharedUserDefaults: .applicationGroup, minTLSVersion: SecurityFlags.minTLSVersion.stringValue, deleteUserLogs: LogFileDestination.deleteAllLogs, - analyticsServiceConfiguration: AnalyticsServiceConfigurationBuilder().build() + analyticsServiceConfiguration: AnalyticsServiceConfigurationBuilder().build(), + countlyProvider: { CountlyWrapper() } ) voIPPushManager.delegate = sessionManager diff --git a/wire-ios/Wire-iOS/Sources/AppStateCalculator.swift b/wire-ios/Wire-iOS/Sources/AppStateCalculator.swift index d0716f2ff54..af1fe13cf27 100644 --- a/wire-ios/Wire-iOS/Sources/AppStateCalculator.swift +++ b/wire-ios/Wire-iOS/Sources/AppStateCalculator.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireSyncEngine enum AppState: Equatable { diff --git a/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift b/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift index db1732613cd..1e2e8c0c439 100644 --- a/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift +++ b/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift @@ -19,6 +19,7 @@ import Foundation import UniformTypeIdentifiers import WireDataModel +import WireLogging import WireReusableUIComponents import WireSyncEngine diff --git a/wire-ios/Wire-iOS/Sources/Authentication/Coordinator/AuthenticationCoordinator.swift b/wire-ios/Wire-iOS/Sources/Authentication/Coordinator/AuthenticationCoordinator.swift index a851a3dd91f..43b86b53fc4 100644 --- a/wire-ios/Wire-iOS/Sources/Authentication/Coordinator/AuthenticationCoordinator.swift +++ b/wire-ios/Wire-iOS/Sources/Authentication/Coordinator/AuthenticationCoordinator.swift @@ -17,6 +17,7 @@ // import UIKit +import WireLogging import WireReusableUIComponents import WireSyncEngine diff --git a/wire-ios/Wire-iOS/Sources/Authentication/Event Handlers/AuthenticationEventResponderChain.swift b/wire-ios/Wire-iOS/Sources/Authentication/Event Handlers/AuthenticationEventResponderChain.swift index 5c3ca6efa6a..2a6d85bc4c7 100644 --- a/wire-ios/Wire-iOS/Sources/Authentication/Event Handlers/AuthenticationEventResponderChain.swift +++ b/wire-ios/Wire-iOS/Sources/Authentication/Event Handlers/AuthenticationEventResponderChain.swift @@ -180,7 +180,11 @@ final class AuthenticationEventResponderChain { /// - parameter eventType: The type of event that occured, and any required context. func handleEvent(ofType eventType: EventType) { - log.info("Event handling manager received event: \(eventType)") + if case .userInput = eventType { + log.info("Event handling manager received event: userInput") + } else { + log.info("Event handling manager received event: \(eventType)") + } switch eventType { case let .flowStart(error, numberOfAccounts): diff --git a/wire-ios/Wire-iOS/Sources/Authentication/Event Handlers/Input/AuthenticationLoginCredentialsInputHandler.swift b/wire-ios/Wire-iOS/Sources/Authentication/Event Handlers/Input/AuthenticationLoginCredentialsInputHandler.swift index 104e09c5895..5f3959695f8 100644 --- a/wire-ios/Wire-iOS/Sources/Authentication/Event Handlers/Input/AuthenticationLoginCredentialsInputHandler.swift +++ b/wire-ios/Wire-iOS/Sources/Authentication/Event Handlers/Input/AuthenticationLoginCredentialsInputHandler.swift @@ -47,7 +47,12 @@ final class AuthenticationLoginCredentialsInputHandler: AuthenticationEventHandl } -struct EmailPasswordInput { +struct EmailPasswordInput: CustomStringConvertible { + var email: String var password: String + + var description: String { + String(describing: Self.self) + } } diff --git a/wire-ios/Wire-iOS/Sources/Authentication/Helpers/PasscodeRules+Shared.swift b/wire-ios/Wire-iOS/Sources/Authentication/Helpers/PasscodeRules+Shared.swift index 27ff674937a..c05b5b60676 100644 --- a/wire-ios/Wire-iOS/Sources/Authentication/Helpers/PasscodeRules+Shared.swift +++ b/wire-ios/Wire-iOS/Sources/Authentication/Helpers/PasscodeRules+Shared.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireUtilities extension PasswordRuleSet { diff --git a/wire-ios/Wire-iOS/Sources/Components/Settings+Logging.swift b/wire-ios/Wire-iOS/Sources/Components/Settings+Logging.swift index 95368361295..f12f2475795 100644 --- a/wire-ios/Wire-iOS/Sources/Components/Settings+Logging.swift +++ b/wire-ios/Wire-iOS/Sources/Components/Settings+Logging.swift @@ -95,7 +95,6 @@ extension Settings { "NetworkStatus", "API Migration", "Cryptobox Migration", - "ContactAddressBook", "ZMUserSession", "ZMClientRegistrationStatus", "UserProfileImageUpdateStatus", diff --git a/wire-ios/Wire-iOS/Sources/Components/Styles/ButtonStyle.swift b/wire-ios/Wire-iOS/Sources/Components/Styles/ButtonStyle.swift index 80f17ed1ecc..39b73efc527 100644 --- a/wire-ios/Wire-iOS/Sources/Components/Styles/ButtonStyle.swift +++ b/wire-ios/Wire-iOS/Sources/Components/Styles/ButtonStyle.swift @@ -19,6 +19,7 @@ import UIKit import WireDesign +@available(*, deprecated, message: "should use WireDesign's WireButtonStyle instead") struct ButtonStyle { typealias ButtonColors = SemanticColors.Button diff --git a/wire-ios/Wire-iOS/Sources/Components/Styles/Stylable.swift b/wire-ios/Wire-iOS/Sources/Components/Styles/Stylable.swift index cb9a33e6f45..f5db5ce4d80 100644 --- a/wire-ios/Wire-iOS/Sources/Components/Styles/Stylable.swift +++ b/wire-ios/Wire-iOS/Sources/Components/Styles/Stylable.swift @@ -16,8 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - // MARK: - Stylable Protocol /// Objects conforming to this protocol opt in to apply a certain style to UI elements diff --git a/wire-ios/Wire-iOS/Sources/Developer/DebugAlert.swift b/wire-ios/Wire-iOS/Sources/Developer/DebugAlert.swift index a275b593829..e8072fd3384 100644 --- a/wire-ios/Wire-iOS/Sources/Developer/DebugAlert.swift +++ b/wire-ios/Wire-iOS/Sources/Developer/DebugAlert.swift @@ -20,6 +20,7 @@ import MessageUI import UIKit import WireCommonComponents import WireDataModel +import WireLogging import WireSystem /// Presents debug alerts diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/ContextItemsProviders/ConversationDeveloperActionsProvider.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/ContextItemsProviders/ConversationDeveloperActionsProvider.swift index 084f5d8fefd..04b12efa4aa 100644 --- a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/ContextItemsProviders/ConversationDeveloperActionsProvider.swift +++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/ContextItemsProviders/ConversationDeveloperActionsProvider.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging import WireUtilities struct ConversationDeveloperActionsProvider: DeveloperToolsContextItemsProvider { diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/ContextItemsProviders/UserClientDeveloperActionsProvider.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/ContextItemsProviders/UserClientDeveloperActionsProvider.swift index 1382ae9e252..af8998a0311 100644 --- a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/ContextItemsProviders/UserClientDeveloperActionsProvider.swift +++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/ContextItemsProviders/UserClientDeveloperActionsProvider.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging /// Provides debug actions for UserClientDetails struct UserClientDeveloperItemsProvider: DeveloperToolsContextItemsProvider { diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModel.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModel.swift index 3b1a55d1b58..12aee0c5289 100644 --- a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModel.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging import WireSyncEngine final class DeveloperDebugActionsViewModel: ObservableObject { @@ -28,6 +29,8 @@ final class DeveloperDebugActionsViewModel: ObservableObject { private let selfClient: UserClient? + private let logger = WireLogger(tag: "developer") + // MARK: - Initialize init(selfClient: UserClient?) { @@ -40,10 +43,12 @@ final class DeveloperDebugActionsViewModel: ObservableObject { buttons = [ .init(title: "Send debug logs", action: sendDebugLogs), .init(title: "Perform quick sync", action: performQuickSync), + .init(title: "Resync resources", action: resyncResources), .init(title: "Break next quick sync", action: breakNextQuickSync), .init(title: "Update Conversation to mixed protocol", action: updateConversationProtocolToMixed), .init(title: "Update Conversation to MLS protocol", action: updateConversationProtocolToMLS), - .init(title: "Update MLS migration status", action: updateMLSMigrationStatus) + .init(title: "Update MLS migration status", action: updateMLSMigrationStatus), + .init(title: "Delete domains in the database", action: deleteDomains) ] } @@ -88,6 +93,12 @@ final class DeveloperDebugActionsViewModel: ObservableObject { } } + // MARK: Resync resources + + private func resyncResources() { + DebugActions.triggerResyncResources() + } + // MARK: Proteus to MLS migration private func updateMLSMigrationStatus() { @@ -159,4 +170,38 @@ final class DeveloperDebugActionsViewModel: ObservableObject { } } + // MARK: Delete domains + + private func deleteDomains() { + guard let syncContext = userSession?.syncContext else { + logger.error("failed to delete domains: no sync context") + return + } + + syncContext.perform { [logger] in + do { + logger.debug("deleted domains of users...") + let users = try syncContext.fetch(NSFetchRequest(entityName: ZMUser.entityName())) + + for user in users where !user.isSelfUser { + user.domain = nil + } + + logger.debug("deleted domains of conversations...") + let conversations = try syncContext + .fetch(NSFetchRequest(entityName: ZMConversation.entityName())) + + for conversation in conversations where conversation.conversationType.isOne(of: .oneOnOne, .group) { + conversation.domain = nil + } + + try syncContext.save() + logger.debug("successfully deleted domains") + + } catch { + logger.error("failed to delete domains: \(error.localizedDescription)") + } + } + } + } diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeepLinks/DeepLinksView.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeepLinks/DeepLinksView.swift index 028530a5e36..1f574c61a01 100644 --- a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeepLinks/DeepLinksView.swift +++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeepLinks/DeepLinksView.swift @@ -29,14 +29,25 @@ struct DeepLinksView: View { // MARK: - Views var body: some View { - List { - Section("Open deeplink") { - TextField("Link", text: $urlString, prompt: Text("Enter deeplink")) - Button("Go") { - viewModel.openLink(urlString: urlString) + VStack(spacing: 0) { + List { + Section("Open deeplink") { + TextField("Link", text: $urlString, prompt: Text("Enter deeplink")) + Button("Go") { + viewModel.openLink(urlString: urlString) + } + .disabled(urlString.isEmpty) } - .disabled(urlString.isEmpty) } + .listStyle(InsetGroupedListStyle()) + .frame(height: 150) + + QRCodeScannerView { scannedCode in + urlString = scannedCode + viewModel.openLink(urlString: scannedCode) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } .alert( isPresented: $viewModel.isShowingAlert, @@ -46,6 +57,21 @@ struct DeepLinksView: View { } } +struct QRCodeScannerView: UIViewControllerRepresentable { + + var onQRCodeScanned: (String) -> Void + + func makeUIViewController(context: Context) -> QRCodeScannerViewController { + let viewController = QRCodeScannerViewController() + viewController.onQRCodeScanned = onQRCodeScanned + return viewController + } + + func updateUIViewController(_ uiViewController: QRCodeScannerViewController, context: Context) { + // Nothing to update here. + } +} + // MARK: - Previews #Preview { diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeepLinks/DeepLinksViewModel.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeepLinks/DeepLinksViewModel.swift index 25f02394914..1e8090891d8 100644 --- a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeepLinks/DeepLinksViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeepLinks/DeepLinksViewModel.swift @@ -55,7 +55,7 @@ final class DeepLinksViewModel: ObservableObject { func openLink(urlString: String) { guard - let url = URL(string: urlString), + let url = URL(string: urlString.trim()), (try? URLAction(url: url)) != nil else { error = .invalidLink diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeepLinks/QRCodeScannerViewController.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeepLinks/QRCodeScannerViewController.swift new file mode 100644 index 00000000000..fb1a38c8a71 --- /dev/null +++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeepLinks/QRCodeScannerViewController.swift @@ -0,0 +1,116 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import AVFoundation +import UIKit + +final class QRCodeScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { + + private var captureSession: AVCaptureSession! + private var previewLayer: AVCaptureVideoPreviewLayer! + var onQRCodeScanned: ((String) -> Void)? + + override func viewDidLoad() { + super.viewDidLoad() + + captureSession = AVCaptureSession() + + guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { + return + } + + let videoInput: AVCaptureDeviceInput + + do { + videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice) + } catch { + return + } + + if captureSession.canAddInput(videoInput) { + captureSession.addInput(videoInput) + } else { + failed() + return + } + + let metadataOutput = AVCaptureMetadataOutput() + + if captureSession.canAddOutput(metadataOutput) { + captureSession.addOutput(metadataOutput) + + metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) + metadataOutput.metadataObjectTypes = [.qr] + } else { + failed() + return + } + + previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + previewLayer.frame = view.layer.bounds + previewLayer.videoGravity = .resizeAspectFill + view.layer.addSublayer(previewLayer) + + DispatchQueue.global(qos: .userInitiated).async { + self.captureSession.startRunning() + } + } + + private func failed() { + let alertController = UIAlertController( + title: "Scanning not supported", + message: "Your device doesn't support QR code scanning.", + preferredStyle: .alert + ) + alertController.addAction(UIAlertAction(title: "OK", style: .default)) + present(alertController, animated: true) + captureSession = nil + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if captureSession.isRunning { + captureSession.stopRunning() + } + } + + func metadataOutput( + _ output: AVCaptureMetadataOutput, + didOutput metadataObjects: [AVMetadataObject], + from connection: AVCaptureConnection + ) { + captureSession.stopRunning() + + if let metadataObject = metadataObjects.first { + guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return } + guard let stringValue = readableObject.stringValue else { return } + + AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) + onQRCodeScanned?(stringValue) + } + } + + override var prefersStatusBarHidden: Bool { + true + } + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + .portrait + } +} diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperE2ei/DeveloperE2eiViewModel.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperE2ei/DeveloperE2eiViewModel.swift index f378a216fcb..4452fee0dbc 100644 --- a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperE2ei/DeveloperE2eiViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperE2ei/DeveloperE2eiViewModel.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireSyncEngine final class DeveloperE2eiViewModel: ObservableObject { diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperToolsView.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperToolsView.swift index 8d2d21c00bc..6e5514081c1 100644 --- a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperToolsView.swift +++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperToolsView.swift @@ -16,7 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Inject import SwiftUI struct DeveloperToolsView: View { @@ -25,8 +24,6 @@ struct DeveloperToolsView: View { @StateObject var viewModel: DeveloperToolsViewModel - @ObserveInjection var inject - // MARK: - Views var body: some View { @@ -38,7 +35,7 @@ struct DeveloperToolsView: View { title: Text(viewModel.alertTitle ?? ""), message: Text(viewModel.alertBody ?? "") ) - }.enableInjection() + } } private func sectionView(for section: DeveloperToolsViewModel.Section) -> some View { diff --git a/wire-ios/Wire-iOS/Sources/Helpers/AuthenticationType.swift b/wire-ios/Wire-iOS/Sources/Helpers/AuthenticationType.swift index 9fe7bbca026..71735a950d4 100644 --- a/wire-ios/Wire-iOS/Sources/Helpers/AuthenticationType.swift +++ b/wire-ios/Wire-iOS/Sources/Helpers/AuthenticationType.swift @@ -17,6 +17,7 @@ // import LocalAuthentication +import WireLogging import WireSystem enum AuthenticationType: CaseIterable { diff --git a/wire-ios/Wire-iOS/Sources/Helpers/PerformanceDebugger.swift b/wire-ios/Wire-iOS/Sources/Helpers/PerformanceDebugger.swift index 9cedd63b09a..2c1802e5a70 100644 --- a/wire-ios/Wire-iOS/Sources/Helpers/PerformanceDebugger.swift +++ b/wire-ios/Wire-iOS/Sources/Helpers/PerformanceDebugger.swift @@ -17,6 +17,7 @@ // import UIKit +import WireLogging import WireSystem /// An object that tracks performance issues in the application for debugging purposes. diff --git a/wire-ios/Wire-iOS/Sources/Helpers/SaveFileManager.swift b/wire-ios/Wire-iOS/Sources/Helpers/SaveFileManager.swift index e50b2e58e33..84ae333d605 100644 --- a/wire-ios/Wire-iOS/Sources/Helpers/SaveFileManager.swift +++ b/wire-ios/Wire-iOS/Sources/Helpers/SaveFileManager.swift @@ -17,6 +17,7 @@ // import UIKit +import WireLogging import WireSystem protocol SaveFileActions { diff --git a/wire-ios/Wire-iOS/Sources/Helpers/TextTransform/String+Transform.swift b/wire-ios/Wire-iOS/Sources/Helpers/TextTransform/String+Transform.swift index a9ddc839ebf..38dee6b9755 100644 --- a/wire-ios/Wire-iOS/Sources/Helpers/TextTransform/String+Transform.swift +++ b/wire-ios/Wire-iOS/Sources/Helpers/TextTransform/String+Transform.swift @@ -46,10 +46,3 @@ extension NSAttributedString { return mutableCopy } } - -extension String { - - func trim() -> String { - trimmingCharacters(in: .whitespaces) - } -} diff --git a/wire-ios/Wire-iOS/Sources/Helpers/TmpFiles/TemporaryFileService.swift b/wire-ios/Wire-iOS/Sources/Helpers/TmpFiles/TemporaryFileService.swift index 98feab6f52a..6902db30aad 100644 --- a/wire-ios/Wire-iOS/Sources/Helpers/TmpFiles/TemporaryFileService.swift +++ b/wire-ios/Wire-iOS/Sources/Helpers/TmpFiles/TemporaryFileService.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireSystem protocol TemporaryFileServiceInterface { diff --git a/wire-ios/Wire-iOS/Sources/Helpers/syncengine/Conversation+Participants.swift b/wire-ios/Wire-iOS/Sources/Helpers/syncengine/Conversation+Participants.swift index 4664c7e47b6..5f87f79ce76 100644 --- a/wire-ios/Wire-iOS/Sources/Helpers/syncengine/Conversation+Participants.swift +++ b/wire-ios/Wire-iOS/Sources/Helpers/syncengine/Conversation+Participants.swift @@ -16,6 +16,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import WireLogging import WireSyncEngine extension GroupDetailsConversation where Self: ZMConversation { diff --git a/wire-ios/Wire-iOS/Sources/LaunchSequenceOperation.swift b/wire-ios/Wire-iOS/Sources/LaunchSequenceOperation.swift index 180603f529d..aa8c0bc59ac 100644 --- a/wire-ios/Wire-iOS/Sources/LaunchSequenceOperation.swift +++ b/wire-ios/Wire-iOS/Sources/LaunchSequenceOperation.swift @@ -20,6 +20,7 @@ import avs import Foundation import WireCommonComponents import WireDesign +import WireLogging import WireSyncEngine // MARK: - LaunchSequenceOperation @@ -68,11 +69,6 @@ final class AVSLoggingOperation: LaunchSequenceOperation { final class AutomationHelperOperation: LaunchSequenceOperation { func execute() { AutomationHelper.sharedHelper.installDebugDataIfNeeded() - - if AutomationHelper.sharedHelper.enableMLSSupport == true { - var flag = DeveloperFlag.enableMLSSupport - flag.isOn = true - } } } diff --git a/wire-ios/Wire-iOS/Sources/Managers/AddressBookHelper.swift b/wire-ios/Wire-iOS/Sources/Managers/AddressBookHelper.swift deleted file mode 100644 index 7b47da6eee0..00000000000 --- a/wire-ios/Wire-iOS/Sources/Managers/AddressBookHelper.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Contacts -import UIKit -import WireSyncEngine - -protocol AddressBookHelperProtocol: AnyObject { - var isAddressBookAccessGranted: Bool { get } - var isAddressBookAccessUnknown: Bool { get } - var isAddressBookAccessDisabled: Bool { get } - var accessStatusDidChangeToGranted: Bool { get } - - static var sharedHelper: AddressBookHelperProtocol { get } - - func requestPermissions(_ callback: ((Bool) -> Void)?) - func persistCurrentAccessStatus() -} - -/// Allows access to address book for search -final class AddressBookHelper: AddressBookHelperProtocol { - - /// Singleton - static var sharedHelper: AddressBookHelperProtocol = AddressBookHelper() - - // MARK: - Constants - - private let addressBookLastAccessStatusKey = "AddressBookLastAccessStatus" - - // MARK: - Permissions - - var isAddressBookAccessUnknown: Bool { - CNContactStore.authorizationStatus(for: .contacts) == .notDetermined - } - - var isAddressBookAccessGranted: Bool { - CNContactStore.authorizationStatus(for: .contacts) == .authorized - } - - var isAddressBookAccessDisabled: Bool { - CNContactStore.authorizationStatus(for: .contacts) == .denied - } - - /// Request access to the user. Will asynchronously invoke the callback passing as argument - /// whether access was granted. - func requestPermissions(_ callback: ((Bool) -> Void)?) { - CNContactStore().requestAccess(for: .contacts, completionHandler: { [weak self] authorized, _ in - DispatchQueue.main.async { - self?.persistCurrentAccessStatus() - callback?(authorized) - } - }) - } - - // MARK: – Access Status Change Detection - - func persistCurrentAccessStatus() { - let status = CNContactStore.authorizationStatus(for: .contacts).rawValue as Int - UserDefaults.standard.set(NSNumber(value: status), forKey: addressBookLastAccessStatusKey) - } - - private var lastAccessStatus: CNAuthorizationStatus? { - guard let value = UserDefaults.standard.object(forKey: addressBookLastAccessStatusKey) as? NSNumber - else { return nil } - return CNAuthorizationStatus(rawValue: value.intValue) - } - - var accessStatusDidChangeToGranted: Bool { - guard let lastStatus = lastAccessStatus else { return false } - return CNContactStore.authorizationStatus(for: .contacts) != lastStatus && isAddressBookAccessGranted - } - -} diff --git a/wire-ios/Wire-iOS/Sources/Managers/Image/ImagePickerManager.swift b/wire-ios/Wire-iOS/Sources/Managers/Image/ImagePickerManager.swift index 5b175778706..53fde9d67f0 100644 --- a/wire-ios/Wire-iOS/Sources/Managers/Image/ImagePickerManager.swift +++ b/wire-ios/Wire-iOS/Sources/Managers/Image/ImagePickerManager.swift @@ -19,6 +19,7 @@ import MobileCoreServices import UIKit import UniformTypeIdentifiers +import WireFoundation import WireSyncEngine extension UIImage { @@ -34,24 +35,26 @@ class ImagePickerManager: NSObject { // MARK: - Properties - private weak var viewController: UIViewController? - private var sourceType: UIImagePickerController.SourceType? private var completion: ((UIImage) -> Void)? private let mediaShareRestrictionManager = MediaShareRestrictionManager(sessionRestriction: ZMUserSession.shared()) + private let device = DeviceWrapper(device: .current) // MARK: - Methods func showActionSheet( on viewController: UIViewController? = UIApplication.shared.topmostViewController(onlyFullScreen: false), + popoverConfiguration: PopoverPresentationControllerConfiguration, completion: @escaping (UIImage) -> Void ) -> UIAlertController { self.completion = completion - self.viewController = viewController - return imagePickerAlert() + return imagePickerAlert(viewController: viewController, popoverConfiguration: popoverConfiguration) } - private func imagePickerAlert() -> UIAlertController { + private func imagePickerAlert( + viewController: UIViewController?, + popoverConfiguration: PopoverPresentationControllerConfiguration + ) -> UIAlertController { typealias Alert = L10n.Localizable.Self.Settings.AccountPictureGroup.Alert let actionSheet = UIAlertController( title: Alert.title, @@ -62,16 +65,30 @@ class ImagePickerManager: NSObject { // Choose from gallery option, if security flag enabled if mediaShareRestrictionManager.isPhotoLibraryEnabled { let galleryAction = UIAlertAction(title: Alert.choosePicture, style: .default) { [weak self] _ in - self?.sourceType = .photoLibrary - self?.getImage(fromSourceType: .photoLibrary) + guard let self, let viewController else { return } + + Task { @MainActor in + self.getImage( + fromSourceType: .photoLibrary, + viewController: viewController, + popoverConfiguration: popoverConfiguration + ) + } } actionSheet.addAction(galleryAction) } // Take photo let cameraAction = UIAlertAction(title: Alert.takePicture, style: .default) { [weak self] _ in - self?.sourceType = .camera - self?.getImage(fromSourceType: .camera) + guard let self, let viewController else { return } + + Task { @MainActor in + self.getImage( + fromSourceType: .camera, + viewController: viewController, + popoverConfiguration: popoverConfiguration + ) + } } actionSheet.addAction(cameraAction) @@ -81,11 +98,13 @@ class ImagePickerManager: NSObject { return actionSheet } - private func getImage(fromSourceType sourceType: UIImagePickerController.SourceType) { - guard UIImagePickerController.isSourceTypeAvailable(sourceType), - let viewController else { - return - } + @MainActor + private func getImage( + fromSourceType sourceType: UIImagePickerController.SourceType, + viewController: UIViewController, + popoverConfiguration: PopoverPresentationControllerConfiguration + ) { + guard UIImagePickerController.isSourceTypeAvailable(sourceType) else { return } let imagePickerController = UIImagePickerController() imagePickerController.delegate = self @@ -101,24 +120,12 @@ class ImagePickerManager: NSObject { imagePickerController.cameraDevice = .front imagePickerController.modalTransitionStyle = .coverVertical case .photoLibrary, .savedPhotosAlbum: - if viewController.isIPadRegular() { + if device.userInterfaceIdiom == .pad { + // UIKit will crash if the photo library is not presented using a popoverPresentationController on iPad + // https://developer.apple.com/documentation/uikit/uiimagepickercontroller imagePickerController.modalPresentationStyle = .popover - - if let popoverPresentationController = imagePickerController.popoverPresentationController { - popoverPresentationController.backgroundColor = UIColor.white - - // UIKit will crash if the photo library is not presented using a popoverPresentationController - // https://developer.apple.com/documentation/uikit/uiimagepickercontroller - // TODO: [WPB-11605] fix this workaround and choose proper sourceView/sourceRect - popoverPresentationController.sourceView = viewController.view - popoverPresentationController.sourceRect = .init( - origin: .init( - x: viewController.view.safeAreaLayoutGuide.layoutFrame.maxX, - y: viewController.view.safeAreaLayoutGuide.layoutFrame.minY - ), - size: .zero - ) - } + imagePickerController.popoverPresentationController?.backgroundColor = .white + imagePickerController.configurePopoverPresentationController(using: popoverConfiguration) } default: break diff --git a/wire-ios/Wire-iOS/Sources/Managers/Image/ProfileImagePickerManager.swift b/wire-ios/Wire-iOS/Sources/Managers/Image/ProfileImagePickerManager.swift index a44d1940f51..4af3b594bfe 100644 --- a/wire-ios/Wire-iOS/Sources/Managers/Image/ProfileImagePickerManager.swift +++ b/wire-ios/Wire-iOS/Sources/Managers/Image/ProfileImagePickerManager.swift @@ -21,13 +21,12 @@ import WireSyncEngine final class ProfileImagePickerManager: ImagePickerManager { - func selectProfileImage() -> UIAlertController { - showActionSheet { image in - guard let jpegData = image.jpegData else { - return - } - ZMUserSession.shared()?.enqueue { - ZMUserSession.shared()?.userProfileImage.updateImage(imageData: jpegData) + func selectProfileImage(popoverConfiguration: PopoverPresentationControllerConfiguration) -> UIAlertController { + showActionSheet(popoverConfiguration: popoverConfiguration) { image in + guard let jpegData = image.jpegData, let session = ZMUserSession.shared() else { return } + + session.enqueue { + session.userProfileImage.updateImage(imageData: jpegData) } } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/AddContacts/AddParticipantsViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/AddContacts/AddParticipantsViewController.swift index 79cc77a5f09..a60c836c0ad 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/AddContacts/AddParticipantsViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/AddContacts/AddParticipantsViewController.swift @@ -358,13 +358,11 @@ final class AddParticipantsViewController: UIViewController { private func updateSelectionValues() { // Update view model after selection changed if case let .create(values) = viewModel.context { - let mlsFeature = userSession.makeGetMLSFeatureUseCase().invoke() let updated = ConversationCreationValues( name: values.name, participants: userSelection.users, allowGuests: true, - allowServices: true, - encryptionProtocol: mlsFeature.config.defaultProtocol, + encryptionProtocol: userSession.defaultProtocol, selfUser: userSession.selfUser ) viewModel = AddParticipantsViewModel(with: .create(updated)) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/BlockerViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/BlockerViewController.swift index c17701bfefa..a24a5f9c465 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/BlockerViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/BlockerViewController.swift @@ -18,6 +18,7 @@ import MessageUI import UIKit +import WireLogging import WireSyncEngine enum BlockerViewControllerContext { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallGridView/CallParticipantViews/AVSVideoContainerView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallGridView/CallParticipantViews/AVSVideoContainerView.swift index d13a32dc2ef..132621e8cc1 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallGridView/CallParticipantViews/AVSVideoContainerView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallGridView/CallParticipantViews/AVSVideoContainerView.swift @@ -17,7 +17,7 @@ // import UIKit -import struct WireSystem.WireLogger +import WireLogging /// A placeholder container for AVSVideo to start the rendering only if the view is instantiated and setup. final class AVSVideoContainerView: UIView { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Collections/CollectionsViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Collections/CollectionsViewController.swift index a692a73e876..54eaec6788a 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Collections/CollectionsViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Collections/CollectionsViewController.swift @@ -17,6 +17,7 @@ // import UIKit +import WireLogging import WireMainNavigationUI import WireSyncEngine diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/NetworkStatus/NetworkStatusView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/NetworkStatus/NetworkStatusView.swift index 12fd18d2563..4568e964c59 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/NetworkStatus/NetworkStatusView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/NetworkStatus/NetworkStatusView.swift @@ -18,6 +18,7 @@ import UIKit import WireDesign +import WireLogging import WireSystem enum NetworkStatusViewState { @@ -118,7 +119,7 @@ final class NetworkStatusView: UIView { } self.state = .online - backgroundColor = SemanticColors.View.backgroundDefault + backgroundColor = ColorTheme.Backgrounds.surface createConstraints() } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsCell.swift deleted file mode 100644 index 30a75789cd2..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsCell.swift +++ /dev/null @@ -1,246 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit -import WireCommonComponents -import WireDesign -import WireSyncEngine - -typealias ContactsCellActionButtonHandler = (UserType, ContactsCell.Action) -> Void - -/// A UITableViewCell version of UserCell, with simpler functionality for contact Screen with table view index bar -final class ContactsCell: UITableViewCell, SeparatorViewProtocol { - var user: UserType? { - didSet { - avatar.user = user - updateTitleLabel() - - if let subtitle = subtitle(forRegularUser: user), subtitle.length > 0 { - subtitleLabel.isHidden = false - subtitleLabel.attributedText = subtitle - } else { - subtitleLabel.isHidden = true - } - } - } - - static let boldFont: FontSpec = .smallRegularFont - static let lightFont: FontSpec = .smallLightFont - - typealias ViewColors = SemanticColors.View - typealias LabelColors = SemanticColors.Label - - let avatar: BadgeUserImageView = { - let badgeUserImageView = BadgeUserImageView() - badgeUserImageView.userSession = ZMUserSession.shared() - badgeUserImageView.initialsFont = .avatarInitial - badgeUserImageView.size = .small - badgeUserImageView.translatesAutoresizingMaskIntoConstraints = false - - return badgeUserImageView - }() - - let avatarSpacer = UIView() - let buttonSpacer = UIView() - - let titleLabel: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.font = FontSpec.normalLightFont.font! - label.accessibilityIdentifier = "contact_cell.name" - - return label - }() - - let subtitleLabel: UILabel = { - let label = UILabel() - label.translatesAutoresizingMaskIntoConstraints = false - label.font = FontSpec.smallRegularFont.font! - label.accessibilityIdentifier = "contact_cell.username" - - return label - }() - - var action: Action? { - didSet { - actionButton.setTitle(action?.localizedDescription, for: .normal) - } - } - - let actionButton = ZMButton( - style: .accentColorTextButtonStyle, - cornerRadius: 4, - fontSpec: .mediumSemiboldFont - ) - - var actionButtonHandler: ContactsCellActionButtonHandler? - - private lazy var actionButtonWidth: CGFloat = { - guard let font = actionButton.titleLabel?.font else { return 0 } - - let transform = actionButton.textTransform - let insets = actionButton.contentEdgeInsets - - let titleWidths: [CGFloat] = [Action.open, .invite].map { - let title = $0.localizedDescription - let transformedTitle = title.applying(transform: transform) - return transformedTitle.size(withAttributes: [.font: font]).width - } - - let maxWidth = titleWidths.max()! - return CGFloat(ceilf(Float(insets.left + maxWidth + insets.right))) - }() - - var titleStackView: UIStackView! - var contentStackView: UIStackView! - - // SeparatorCollectionViewCell - let separator = UIView() - var separatorInsetConstraint: NSLayoutConstraint! - var separatorLeadingInset: CGFloat = 64 { - didSet { - separatorInsetConstraint?.constant = separatorLeadingInset - } - } - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - configureSubviews() - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func setUp() { - avatarSpacer.addSubview(avatar) - avatarSpacer.translatesAutoresizingMaskIntoConstraints = false - - buttonSpacer.addSubview(actionButton) - buttonSpacer.translatesAutoresizingMaskIntoConstraints = false - - titleStackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel]) - titleStackView.axis = .vertical - titleStackView.distribution = .equalSpacing - titleStackView.alignment = .leading - titleStackView.translatesAutoresizingMaskIntoConstraints = false - - contentStackView = UIStackView(arrangedSubviews: [avatarSpacer, titleStackView, buttonSpacer]) - contentStackView.axis = .horizontal - contentStackView.distribution = .fill - contentStackView.alignment = .center - contentStackView.translatesAutoresizingMaskIntoConstraints = false - - contentView.addSubview(contentStackView) - - createConstraints() - - actionButton.addTarget(self, action: #selector(ContactsCell.actionButtonPressed(sender:)), for: .touchUpInside) - } - - private func configureSubviews() { - - setUp() - - separator.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(separator) - - createSeparatorConstraints() - - separator.backgroundColor = ViewColors.backgroundSeparatorCell - - backgroundColor = ViewColors.backgroundUserCell - - titleLabel.textColor = LabelColors.textDefault - subtitleLabel.textColor = LabelColors.textCellSubtitle - - updateTitleLabel() - } - - private func createConstraints() { - - let buttonMargin: CGFloat = 16 - - NSLayoutConstraint.activate([ - avatar.widthAnchor.constraint(equalToConstant: 28), - avatar.heightAnchor.constraint(equalToConstant: 28), - avatarSpacer.widthAnchor.constraint(equalToConstant: 64), - avatarSpacer.heightAnchor.constraint(equalTo: avatar.heightAnchor), - avatarSpacer.centerXAnchor.constraint(equalTo: avatar.centerXAnchor), - avatarSpacer.centerYAnchor.constraint(equalTo: avatar.centerYAnchor), - contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor), - contentStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - contentStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -buttonMargin) - ]) - - [actionButton, buttonSpacer].forEach { $0.translatesAutoresizingMaskIntoConstraints = false } - NSLayoutConstraint.activate([ - buttonSpacer.topAnchor.constraint(equalTo: actionButton.topAnchor), - buttonSpacer.bottomAnchor.constraint(equalTo: actionButton.bottomAnchor), - - actionButton.widthAnchor.constraint(equalToConstant: actionButtonWidth), - buttonSpacer.trailingAnchor.constraint(equalTo: actionButton.trailingAnchor), - buttonSpacer.leadingAnchor.constraint(equalTo: actionButton.leadingAnchor, constant: -buttonMargin) - ]) - } - - private func updateTitleLabel() { - guard let user, let selfUser = ZMUser.selfUser() else { - return - } - - let userStatus = UserStatus(user: user, isE2EICertified: false) - titleLabel.attributedText = userStatus.title( - color: LabelColors.textDefault, - includeAvailability: selfUser.isTeamMember, - includeVerificationStatus: false, - appendYouSuffix: false - ) - } - - @objc - func actionButtonPressed(sender: Any?) { - if let user, let action { - actionButtonHandler?(user, action) - } - } -} - -extension ContactsCell: UserCellSubtitleProtocol {} - -extension ContactsCell { - - typealias ContactsUIActionButton = L10n.Localizable.ContactsUi.ActionButton - - enum Action { - - case open - case invite - - var localizedDescription: String { - switch self { - case .open: - ContactsUIActionButton.open.capitalized - case .invite: - ContactsUIActionButton.invite.capitalized - } - } - } -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsDataSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsDataSource.swift deleted file mode 100644 index 9d292acdbb4..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsDataSource.swift +++ /dev/null @@ -1,161 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Foundation -import WireDataModel -import WireSyncEngine - -protocol ContactsDataSourceDelegate: AnyObject { - - func dataSource(_ dataSource: ContactsDataSource, cellFor user: UserType, at indexPath: IndexPath) - -> UITableViewCell - func dataSource(_ dataSource: ContactsDataSource, didReceiveSearchResult newUser: [UserType]) - -} - -final class ContactsDataSource: NSObject { - - static let MinimumNumberOfContactsToDisplaySections: UInt = 15 - - weak var delegate: ContactsDataSourceDelegate? - - private(set) var searchDirectory: SearchDirectory? - private var sections = [[UserType]]() - private var collation: UILocalizedIndexedCollation { .current() } - - // MARK: - Life Cycle - - override init() { - super.init() - self.searchDirectory = ZMUserSession.shared().map(SearchDirectory.init) - performSearch() - } - - deinit { - searchDirectory?.tearDown() - } - - // MARK: - Getters / Setters - - var ungroupedSearchResults = [UserType]() { - didSet { - recalculateSections() - } - } - - var searchQuery: String = "" { - didSet { - performSearch() - } - } - - var shouldShowSectionIndex: Bool { - ungroupedSearchResults.count >= type(of: self).MinimumNumberOfContactsToDisplaySections - } - - // MARK: - Methods - - private func performSearch() { - guard let searchDirectory else { return } - - let request = SearchRequest(query: searchQuery, searchOptions: [.contacts, .addressBook]) - let task = searchDirectory.perform(request) - - task.addResultHandler { [weak self] searchResult, _ in - guard let self else { return } - ungroupedSearchResults = searchResult.addressBook - delegate?.dataSource(self, didReceiveSearchResult: searchResult.addressBook) - } - - task.start() - } - - func user(at indexPath: IndexPath) -> UserType { - section(at: indexPath.section)[indexPath.row] - } - - private func section(at index: Int) -> [UserType] { - sections[index] - } - - private func recalculateSections() { - let nameSelector = #selector(getter: UserType.name) - - guard shouldShowSectionIndex else { - let sortedResults = collation.sortedArray( - from: ungroupedSearchResults, - collationStringSelector: nameSelector - ) - sections = [sortedResults] as? [[UserType]] ?? [] - return - } - - let numberOfSections = collation.sectionTitles.count - let emptySections = Array(repeating: [UserType](), count: numberOfSections) - - let unsortedSections = ungroupedSearchResults.reduce(into: emptySections) { sections, user in - let index = collation.section(for: user, collationStringSelector: nameSelector) - sections[index].append(user) - } - - let sortedSections = unsortedSections.map { - collation.sortedArray(from: $0, collationStringSelector: nameSelector) - } - - sections = sortedSections as? [[UserType]] ?? [] - } -} - -extension ContactsDataSource: UITableViewDataSource { - - func numberOfSections(in tableView: UITableView) -> Int { - sections.count - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - self.section(at: section).count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - delegate?.dataSource(self, cellFor: user(at: indexPath), at: indexPath) ?? UITableViewCell() - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - guard shouldShowSectionIndex, !self.section(at: section).isEmpty else { return nil } - return collation.sectionTitles[section] - } - - func sectionIndexTitles(for tableView: UITableView) -> [String]? { - collation.sectionIndexTitles - } - - func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { - collation.section(forSectionIndexTitle: index) - } -} - -extension ContactsDataSource: UITableViewDelegate { - - func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - false - } - - func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { - false - } -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsSectionHeaderView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsSectionHeaderView.swift deleted file mode 100644 index 78aa01f8c3c..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsSectionHeaderView.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit - -final class ContactsSectionHeaderView: UITableViewHeaderFooterView { - let label: UILabel = { - let label = UILabel() - label.font = .smallSemiboldFont - label.textColor = .from(scheme: .textForeground, variant: .dark) - - return label - }() - - static let height: CGFloat = 20 - - override init(reuseIdentifier: String?) { - super.init(reuseIdentifier: reuseIdentifier) - - let blurEffect = UIBlurEffect(style: .light) - let blurEffectView = UIVisualEffectView(effect: blurEffect) - blurEffectView.frame = bounds - blurEffectView.backgroundColor = .clear - - backgroundView = blurEffectView - - setupSubviews() - setupConstraints() - setupStyle() - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupSubviews() { - contentView.addSubview(label) - } - - func setupStyle() { - textLabel?.isHidden = true - } - - private func setupConstraints() { - - label.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - label.centerYAnchor.constraint(equalTo: centerYAnchor), - label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 24) - ]) - } - - override var intrinsicContentSize: CGSize { - CGSize(width: UIView.noIntrinsicMetric, height: ContactsSectionHeaderView.height) - } - -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+Constraints.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+Constraints.swift deleted file mode 100644 index 6a6ed7a2856..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+Constraints.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit - -extension ContactsViewController { - - func setupLayout() { - [ - searchHeaderViewController.view, - separatorView, - tableView, - emptyResultsLabel, - inviteOthersButton, - noContactsLabel, - bottomContainerSeparatorView, - bottomContainerView - ].forEach { - $0.translatesAutoresizingMaskIntoConstraints = false - } - - let standardOffset: CGFloat = 24.0 - var constraints: [NSLayoutConstraint] = [] - - constraints += [ - searchHeaderViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - searchHeaderViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - searchHeaderViewController.view.topAnchor.constraint(equalTo: view.topAnchor), - searchHeaderViewController.view.bottomAnchor.constraint(equalTo: separatorView.topAnchor) - ] - - constraints += [ - separatorView.leadingAnchor.constraint( - equalTo: separatorView.superview!.leadingAnchor, - constant: standardOffset - ), - separatorView.trailingAnchor.constraint( - equalTo: separatorView.superview!.trailingAnchor, - constant: -standardOffset - ), - separatorView.heightAnchor.constraint(equalToConstant: 0.5), - separatorView.bottomAnchor.constraint(equalTo: tableView.topAnchor), - - tableView.leadingAnchor.constraint(equalTo: tableView.superview!.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: tableView.superview!.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: bottomContainerView.topAnchor) - ] - - constraints += [ - emptyResultsLabel.centerXAnchor.constraint(equalTo: tableView.centerXAnchor), - emptyResultsLabel.centerYAnchor.constraint(equalTo: tableView.centerYAnchor) - ] - - constraints += [ - noContactsLabel.topAnchor.constraint( - equalTo: searchHeaderViewController.view.bottomAnchor, - constant: standardOffset - ), - noContactsLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: standardOffset), - noContactsLabel.trailingAnchor.constraint(equalTo: noContactsLabel.superview!.trailingAnchor) - ] - - constraints += [ - bottomContainerView.leadingAnchor.constraint(equalTo: bottomContainerView.superview!.leadingAnchor), - bottomContainerView.trailingAnchor.constraint(equalTo: bottomContainerView.superview!.trailingAnchor), - bottomContainerSeparatorView.topAnchor - .constraint(equalTo: bottomContainerSeparatorView.superview!.topAnchor), - bottomContainerSeparatorView.leadingAnchor - .constraint(equalTo: bottomContainerSeparatorView.superview!.leadingAnchor), - bottomContainerSeparatorView.trailingAnchor - .constraint(equalTo: bottomContainerSeparatorView.superview!.trailingAnchor), - bottomContainerSeparatorView.heightAnchor.constraint(equalToConstant: 0.5) - ] - - guard let superview = inviteOthersButton.superview else { - assertionFailure("inviteOthersButton must have a superview before layout is set") - return - } - - let keyboardConstraint = view.keyboardLayoutGuide.topAnchor - .constraint(equalTo: bottomContainerView.bottomAnchor) - keyboardConstraint.priority = .defaultHigh - keyboardConstraint.isActive = true - // This is necessary to allow the various edge constraints to engage. - view.keyboardLayoutGuide.followsUndockedKeyboard = true - - let bottomInset = superview.safeAreaInsets.bottom - let bottomEdgeConstraint = inviteOthersButton.bottomAnchor.constraint( - equalTo: superview.bottomAnchor, - constant: -(standardOffset / 2.0 + bottomInset) - ) - - constraints += [ - bottomEdgeConstraint, - inviteOthersButton.topAnchor.constraint(equalTo: superview.topAnchor, constant: standardOffset / 2), - inviteOthersButton.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: standardOffset), - inviteOthersButton.trailingAnchor.constraint( - equalTo: superview.trailingAnchor, - constant: -standardOffset - ) - ] - - constraints += [inviteOthersButton.heightAnchor.constraint(equalToConstant: 56)] - - NSLayoutConstraint.activate(constraints) - } -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+ContactsDataSourceDelegate.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+ContactsDataSourceDelegate.swift deleted file mode 100644 index b67027e71cd..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+ContactsDataSourceDelegate.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit -import WireDataModel - -extension ContactsViewController: ContactsDataSourceDelegate { - - func dataSource( - _ dataSource: ContactsDataSource, - cellFor user: UserType, - at indexPath: IndexPath - ) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(ofType: ContactsCell.self, for: indexPath) - cell.user = user - - cell.actionButtonHandler = { [weak self] user, action in - switch action { - case .open: - self?.openConversation(for: user) - case .invite: - self?.invite(user: user) - } - } - - if !cell.actionButton.isHidden { - cell.action = user.isConnected ? .open : .invite - } - - return cell - } - - func dataSource(_ dataSource: ContactsDataSource, didReceiveSearchResult newUser: [UserType]) { - tableView.reloadData() - updateEmptyResults(hasResults: !newUser.isEmpty) - } - -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+Invite.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+Invite.swift deleted file mode 100644 index e91afa5a420..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+Invite.swift +++ /dev/null @@ -1,179 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit -import WireDataModel -import WireFoundation -import WireSystem - -private let zmLog = ZMSLog(tag: "UI") - -extension ContactsViewController { - - private var canInviteByEmail: Bool { - ZMAddressBookContact.canInviteLocallyWithEmail() - } - - private var canInviteByPhone: Bool { - ZMAddressBookContact.canInviteLocallyWithPhoneNumber() - } - - @objc - func sendIndirectInvite(_ sender: UIButton) { - let shareItemProvider = ShareItemProvider(placeholderItem: "") - let activityController = UIActivityViewController( - activityItems: [shareItemProvider], - applicationActivities: nil - ) - activityController.excludedActivityTypes = [UIActivity.ActivityType.airDrop] - if let popoverPresentationController = activityController.popoverPresentationController { - let margin = 2 * fmin(sender.frame.origin.x, sender.frame.origin.y) - popoverPresentationController.sourceView = sender.superview - popoverPresentationController.sourceRect = sender.frame.insetBy(dx: -margin, dy: -margin) - } - present(activityController, animated: true) - } - - func openConversation(for user: UserType) { - guard - user.isConnected, - let conversation = user.oneToOneConversation - else { return } - - let showConversation: Completion = { - ZClientViewController.shared?.select(conversation: conversation, focusOnView: true, animated: true) - } - - if let navigationController { - navigationController.popToRootViewController(animated: false, completion: showConversation) - } else { - showConversation() - } - } - - func invite(user: UserType) { - do { - guard let contact = (user as? ZMSearchUser)?.contact else { throw InvitationError.noContactInformation } - try invite(contact: contact, from: view) - } catch let InvitationError.canNotSend(client) { - present(unableToSendController(client: client), animated: true) - } catch { - zmLog.error("Could not invite contact: \(error.localizedDescription)") - } - } - - private func invite(contact: ZMAddressBookContact, from view: UIView) throws { - switch contact.contactDetails.count { - case 1: - try inviteWithSingleAddress(for: contact) - case 2...: - let actionSheet = try addressActionSheet(for: contact, in: view) - present(actionSheet, animated: true) - default: - throw InvitationError.noContactInformation - } - } - - private func inviteWithSingleAddress(for contact: ZMAddressBookContact) throws { - if let emailAddress = contact.emailAddresses.first { - guard canInviteByEmail else { throw InvitationError.canNotSend(.email) } - contact.inviteLocallyWithEmail(emailAddress) - - } else if let phoneNumber = contact.rawPhoneNumbers.first { - guard canInviteByPhone else { throw InvitationError.canNotSend(.sms) } - contact.inviteLocallyWithPhoneNumber(phoneNumber) - - } else { - throw InvitationError.noContactInformation - } - } - - private func addressActionSheet(for contact: ZMAddressBookContact, in view: UIView) throws -> UIAlertController { - guard canInviteByEmail || canInviteByPhone else { throw InvitationError.canNotSend(.any) } - - let chooseContactDetailController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - - let presentationController = chooseContactDetailController.popoverPresentationController - presentationController?.sourceView = view - presentationController?.sourceRect = view.bounds - - var actions = [UIAlertAction]() - - if canInviteByEmail { - actions.append(contentsOf: contact.emailAddresses.map { address in - UIAlertAction(title: address, style: .default) { _ in - contact.inviteLocallyWithEmail(address) - chooseContactDetailController.dismiss(animated: true) - } - }) - } - - if canInviteByPhone { - actions.append(contentsOf: contact.rawPhoneNumbers.map { number in - UIAlertAction(title: number, style: .default) { _ in - contact.inviteLocallyWithPhoneNumber(number) - chooseContactDetailController.dismiss(animated: true) - } - }) - } - - actions - .append(UIAlertAction( - title: L10n.Localizable.ContactsUi.InviteSheet.cancelButtonTitle, - style: .cancel - ) { _ in - chooseContactDetailController.dismiss(animated: true) - }) - - actions.forEach(chooseContactDetailController.addAction) - return chooseContactDetailController - } - - private func unableToSendController(client: InvitationError.MessageType) -> UIAlertController { - let unableToSendController = UIAlertController(title: nil, message: client.messageKey, preferredStyle: .alert) - - let okAction = UIAlertAction(title: L10n.Localizable.General.ok, style: .cancel) { _ in - unableToSendController.dismiss(animated: true) - } - - unableToSendController.addAction(okAction) - return unableToSendController - } - - private enum InvitationError: Error { - - case canNotSend(MessageType) - case noContactInformation - - enum MessageType { - - case email - case sms - case any - - var messageKey: String { - switch self { - case .email, .any: - L10n.Localizable.Error.Invite.noEmailProvider - case .sms: - L10n.Localizable.Error.Invite.noMessagingProvider - } - } - } - } -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+UITableViewDelegate.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+UITableViewDelegate.swift deleted file mode 100644 index b0afec63dae..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController+UITableViewDelegate.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit - -extension ContactsViewController: UITableViewDelegate { - - func headerTitle(section: Int) -> String? { - dataSource.tableView(tableView, titleForHeaderInSection: section) - } - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - guard let title = headerTitle(section: section), !title.isEmpty else { return nil } - let headerView = tableView.dequeueReusableHeaderFooter(ofType: ContactsSectionHeaderView.self) - headerView.label.text = title - return headerView - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - guard let title = headerTitle(section: section), !title.isEmpty else { return 0 } - return ContactsSectionHeaderView.height - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - CGFloat.StartUI.CellHeight - } - -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController.swift deleted file mode 100644 index 4e2bb2ab28a..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ContactsViewController.swift +++ /dev/null @@ -1,180 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit -import WireDesign - -final class ContactsViewController: UIViewController { - - let dataSource = ContactsDataSource() - - typealias PeoplePicker = L10n.Localizable.Peoplepicker - typealias ContactsUI = L10n.Localizable.ContactsUi - typealias LabelColors = SemanticColors.Label - typealias ViewColors = SemanticColors.View - - let bottomContainerView = UIView() - let bottomContainerSeparatorView = UIView() - let noContactsLabel = DynamicFontLabel( - text: PeoplePicker.noContactsTitle, - style: .body1, - color: LabelColors.textSettingsPasswordPlaceholder - ) - let searchHeaderViewController = SearchHeaderViewController(userSelection: .init()) - let separatorView = UIView() - let tableView = UITableView() - let inviteOthersButton = ZMButton( - style: .accentColorTextButtonStyle, - cornerRadius: 16, - fontSpec: .normalSemiboldFont - ) - let emptyResultsLabel = DynamicFontLabel( - text: PeoplePicker.noMatchingResultsAfterAddressBookUploadTitle, - style: .body1, - color: LabelColors.textSettingsPasswordPlaceholder - ) - - // MARK: - Life Cycle - - init() { - super.init(nibName: nil, bundle: nil) - - dataSource.delegate = self - tableView.dataSource = dataSource - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - setupViews() - setupLayout() - setupStyle() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setupNavigationBarTitle(ContactsUI.title.capitalized) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - showKeyboardIfNeeded() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - _ = searchHeaderViewController.tokenField.resignFirstResponder() - } - - // MARK: - Setup - - private func setupViews() { - setupSearchHeader() - view.addSubview(separatorView) - setupTableView() - setupEmptyResultsLabel() - setupNoContactsLabel() - setupBottomContainer() - } - - private func setupSearchHeader() { - searchHeaderViewController.delegate = self - searchHeaderViewController.allowsMultipleSelection = false - searchHeaderViewController.view.backgroundColor = ViewColors.backgroundDefault - addToSelf(searchHeaderViewController) - } - - private func setupTableView() { - tableView.dataSource = dataSource - tableView.delegate = self - tableView.allowsSelection = false - tableView.rowHeight = 52 - tableView.keyboardDismissMode = .onDrag - tableView.sectionIndexMinimumDisplayRowCount = Int(ContactsDataSource.MinimumNumberOfContactsToDisplaySections) - ContactsCell.register(in: tableView) - ContactsSectionHeaderView.register(in: tableView) - - let bottomContainerHeight: CGFloat = 56.0 + view.safeAreaInsets.bottom - tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottomContainerHeight, right: 0) - view.addSubview(tableView) - } - - private func setupEmptyResultsLabel() { - emptyResultsLabel.textAlignment = .center - view.addSubview(emptyResultsLabel) - } - - private func setupNoContactsLabel() { - view.addSubview(noContactsLabel) - } - - private func setupBottomContainer() { - view.addSubview(bottomContainerView) - bottomContainerView.addSubview(bottomContainerSeparatorView) - - inviteOthersButton.addTarget(self, action: #selector(sendIndirectInvite), for: .touchUpInside) - inviteOthersButton.setTitle(ContactsUI.inviteOthers.capitalized, for: .normal) - bottomContainerView.addSubview(inviteOthersButton) - - } - - private func setupStyle() { - view.backgroundColor = SemanticColors.View.backgroundDefault - - tableView.backgroundColor = SemanticColors.View.backgroundDefault - tableView.separatorStyle = .none - tableView.sectionIndexBackgroundColor = .clear - tableView.sectionIndexColor = .accent() - - bottomContainerSeparatorView.backgroundColor = ViewColors.backgroundSeparatorCell - bottomContainerView.backgroundColor = ViewColors.backgroundUserCell - } - - // MARK: - Methods - - private func showKeyboardIfNeeded() { - if tableView.numberOfTotalRows > StartUIViewController.InitiallyShowsKeyboardConversationThreshold { - _ = searchHeaderViewController.tokenField.becomeFirstResponder() - } - } - - func updateEmptyResults(hasResults: Bool) { - let searchQueryExist = !dataSource.searchQuery.isEmpty - noContactsLabel.isHidden = hasResults || searchQueryExist - setEmptyResultsHidden(hasResults) - } - - private func setEmptyResultsHidden(_ hidden: Bool) { - let completion: (Bool) -> Void = { _ in - self.emptyResultsLabel.isHidden = hidden - self.tableView.isHidden = !hidden - } - - UIView.animate( - withDuration: 0.25, - delay: 0, - options: .beginFromCurrentState, - animations: { self.emptyResultsLabel.alpha = hidden ? 0 : 1 }, - completion: completion - ) - } -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ZMAddressBookContact+LocalInvitation.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ZMAddressBookContact+LocalInvitation.swift deleted file mode 100644 index cba864f937e..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ContactsUI/ZMAddressBookContact+LocalInvitation.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Foundation -import MessageUI -import WireSyncEngine - -final class EmailInvitePresenter: NSObject, MFMailComposeViewControllerDelegate, - MFMessageComposeViewControllerDelegate { - static let sharedInstance: EmailInvitePresenter = .init() - - func mailComposeController( - _ controller: MFMailComposeViewController, - didFinishWith result: MFMailComposeResult, - error: Error? - ) { - controller.dismiss(animated: true, completion: .none) - } - - func messageComposeViewController( - _ controller: MFMessageComposeViewController, - didFinishWith result: MessageComposeResult - ) { - controller.dismiss(animated: true, completion: .none) - } -} - -extension ZMAddressBookContact { - - static func canInviteLocallyWithEmail() -> Bool { - MFMailComposeViewController.canSendMail() - } - - func inviteLocallyWithEmail(_ email: String) { - let composeController = MFMailComposeViewController() - composeController.mailComposeDelegate = EmailInvitePresenter.sharedInstance - composeController.modalPresentationStyle = .formSheet - - composeController.setMessageBody(invitationBody(), isHTML: false) - composeController.setToRecipients([email]) - composeController.presentOverAll(animated: true) - } - - static func canInviteLocallyWithPhoneNumber() -> Bool { - MFMessageComposeViewController.canSendText() - } - - func inviteLocallyWithPhoneNumber(_ phoneNumber: String) { - let composeController = MFMessageComposeViewController() - composeController.messageComposeDelegate = EmailInvitePresenter.sharedInstance - composeController.modalPresentationStyle = .formSheet - composeController.body = invitationBody() - composeController.recipients = [phoneNumber] - composeController.presentOverAll(animated: true) - } - - private func invitationBody() -> String { - guard - let handle = SelfUser.provider?.providedSelfUser.handle - else { - return L10n.Localizable.SendInvitationNoEmail.text - } - - return L10n.Localizable.SendInvitation.text("@" + handle) - } -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation Options/ConversationGuestOptionsViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation Options/ConversationGuestOptionsViewController.swift index e57e6c98f66..1e917307461 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation Options/ConversationGuestOptionsViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation Options/ConversationGuestOptionsViewController.swift @@ -108,7 +108,6 @@ final class ConversationGuestOptionsViewController: UIViewController, tableView.delegate = self tableView.dataSource = self tableView.backgroundColor = SemanticColors.View.backgroundDefault - tableView.contentInsetAdjustmentBehavior = .never } private func setupNavigationBar() { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation Options/ConversationServicesOptionsViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation Options/ConversationServicesOptionsViewController.swift index 01466397332..500396596c8 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation Options/ConversationServicesOptionsViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation Options/ConversationServicesOptionsViewController.swift @@ -78,7 +78,6 @@ final class ConversationServicesOptionsViewController: UIViewController, tableView.delegate = self tableView.dataSource = self tableView.backgroundColor = SemanticColors.View.backgroundDefault - tableView.contentInsetAdjustmentBehavior = .never } private func createConstraints() { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+CanvasViewControllerDelegate.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+CanvasViewControllerDelegate.swift index f0b75723544..dbe6691f0de 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+CanvasViewControllerDelegate.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+CanvasViewControllerDelegate.swift @@ -16,6 +16,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import WireLogging import WireSyncEngine extension ConversationContentViewController: CanvasViewControllerDelegate { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+Forward.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+Forward.swift index 0deb9a6ed6a..c2c4ec92102 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+Forward.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+Forward.swift @@ -19,6 +19,7 @@ import UIKit import WireCommonComponents import WireDesign +import WireLogging import WireSyncEngine extension ZMConversation: ShareDestination { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+MessageAction.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+MessageAction.swift index 6948c1e1846..296f76fa0e3 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+MessageAction.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/ConversationContentViewController+MessageAction.swift @@ -18,6 +18,7 @@ import UIKit import WireDataModel +import WireLogging import WireSyncEngine extension ConversationContentViewController { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController.swift index f30a59e202a..1f51a2bde7b 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController.swift @@ -19,6 +19,7 @@ import UIKit import WireCommonComponents import WireDesign +import WireLogging import WireMainNavigationUI import WireSyncEngine @@ -430,8 +431,11 @@ final class ConversationViewController: UIViewController { assertionFailure("mlsService is missing") return } - - let resolver = OneOnOneResolver(migrator: OneOnOneMigrator(mlsService: mlsService)) + let mlsFeature = await userSession.makeGetMLSFeatureUseCase().invoke() + let resolver = OneOnOneResolver( + migrator: OneOnOneMigrator(mlsService: mlsService), + isMLSEnabled: mlsFeature.isEnabled + ) let resolvedState = try await resolver.resolveOneOnOneConversation(with: otherUserID, in: syncContext) if case let .migratedToMLSGroup(identifier) = resolvedState { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationController.swift index 1480af97fc6..7b1995d2d99 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationController.swift @@ -20,6 +20,7 @@ import UIKit import WireCommonComponents import WireDataModel import WireDesign +import WireLogging import WireSyncEngine protocol ConversationCreationControllerDelegate: AnyObject { @@ -55,10 +56,10 @@ final class ConversationCreationController: UIViewController { ) private lazy var errorSection = ConversationCreateErrorSectionController() - private lazy var optionsSections: [ConversationCreateSectionController] = { + private var optionsSections: [ConversationCreateSectionController] { let sections = [ guestsSection, - servicesSection, + values.shouldIncludeServices ? servicesSection : nil, receiptsSection, shouldIncludeEncryptionProtocolSection ? encryptionProtocolSection : nil ].compactMap { $0 } @@ -68,7 +69,7 @@ final class ConversationCreationController: UIViewController { } return sections - }() + } private var shouldIncludeEncryptionProtocolSection: Bool { if DeveloperFlag.showCreateMLSGroupToggle.isOn { @@ -116,16 +117,34 @@ final class ConversationCreationController: UIViewController { private lazy var encryptionProtocolSection = { let section = ConversationEncryptionProtocolSectionController(values: values) - section.isHidden = true + section.tapAction = { sender in self.presentEncryptionProtocolPicker(sender: sender) { [weak self] encryptionProtocol in - self?.values.encryptionProtocol = encryptionProtocol - self?.updateOptions() + guard let self else { return } + + values.encryptionProtocol = encryptionProtocol + updateOptions() + + reloadOptionsSections() } } return section }() + private func reloadOptionsSections() { + guard let collectionView = collectionViewController.collectionView else { return } + updateSections() + + // ignoring the conversation name so we don't loose the info while testing + let excludedSectionIndex = collectionViewController.sections.startIndex + let endIndex = collectionView.numberOfSections + let sectionsToReload = IndexSet(integersIn: (excludedSectionIndex + 1) ..< endIndex) + + collectionView.performBatchUpdates { + collectionView.reloadSections(sectionsToReload) + } + } + // MARK: - Life cycle init( @@ -134,10 +153,8 @@ final class ConversationCreationController: UIViewController { ) { self.preSelectedParticipants = preSelectedParticipants self.userSession = userSession - - let mlsFeature = userSession.makeGetMLSFeatureUseCase().invoke() self.values = ConversationCreationValues( - encryptionProtocol: mlsFeature.config.defaultProtocol, + encryptionProtocol: userSession.defaultProtocol, selfUser: userSession.selfUser ) @@ -192,12 +209,16 @@ final class ConversationCreationController: UIViewController { ]) collectionViewController.collectionView = collectionView + updateSections() + } + + private func updateSections() { + servicesSection.isHidden = !values.shouldIncludeServices collectionViewController.sections = [nameSection, errorSection] if userSession.selfUser.isTeamMember { collectionViewController.sections.append(contentsOf: optionsSections) } - } private func setupNavigationBar() { @@ -288,7 +309,7 @@ extension ConversationCreationController: AddParticipantsConversationCreationDel name: values.name, users: Set(users), allowGuests: values.allowGuests, - allowServices: values.allowServices, + allowServices: values.shouldIncludeServices ? values.allowServices : false, enableReceipts: values.enableReceipts, messageProtocol: messageProtocol ) { [weak self] result in @@ -422,7 +443,7 @@ extension ConversationCreationController { func presentEncryptionProtocolPicker( sender: UIView, - _ completion: @escaping (Feature.MLS.Config.MessageProtocol) -> Void + _ completion: @escaping (MessageProtocol) -> Void ) { let alertController = encryptionProtocolPicker { type in completion(type) @@ -435,14 +456,13 @@ extension ConversationCreationController { present(alertController, animated: true) } - func encryptionProtocolPicker(_ completion: @escaping (Feature.MLS.Config.MessageProtocol) -> Void) + func encryptionProtocolPicker(_ completion: @escaping (MessageProtocol) -> Void) -> UIAlertController { typealias Localizable = L10n.Localizable.Conversation.Create - let mlsFeature = userSession.makeGetMLSFeatureUseCase().invoke() - let proteus = mlsFeature.config.defaultProtocol == .proteus ? Localizable.ProtocolSelection + let proteus = userSession.defaultProtocol == .proteus ? Localizable.ProtocolSelection .proteusDefault : Localizable.ProtocolSelection.proteus - let mls = mlsFeature.config.defaultProtocol == .mls ? Localizable.ProtocolSelection.mlsDefault : Localizable + let mls = userSession.defaultProtocol == .mls ? Localizable.ProtocolSelection.mlsDefault : Localizable .ProtocolSelection.mls let alert = UIAlertController( diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift index feaddd22692..bb60853120b 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift @@ -36,7 +36,15 @@ final class ConversationCreationValues { var allowGuests: Bool var allowServices: Bool var enableReceipts: Bool - var encryptionProtocol: Feature.MLS.Config.MessageProtocol + var encryptionProtocol: MessageProtocol { + didSet { + allowServices = shouldIncludeServices + } + } + + var shouldIncludeServices: Bool { + encryptionProtocol.supportsBots + } var participants: UserSet { get { @@ -67,7 +75,7 @@ final class ConversationCreationValues { allowGuests: Bool = true, allowServices: Bool = true, enableReceipts: Bool = true, - encryptionProtocol: Feature.MLS.Config.MessageProtocol, + encryptionProtocol: MessageProtocol, selfUser: UserType ) { self.name = name diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/AudioRecorder.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/AudioRecorder.swift index 8123209ddf3..a9eb605c8e6 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/AudioRecorder.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/AudioRecorder.swift @@ -19,6 +19,7 @@ import avs import Foundation import MediaPlayer +import WireLogging import WireSyncEngine enum PlayingState: UInt, CustomStringConvertible { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+Camera.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+Camera.swift index 0960c51cb6e..2726048c6a0 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+Camera.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/ConversationInputBarViewController+Camera.swift @@ -20,6 +20,7 @@ import FLAnimatedImage import MobileCoreServices import Photos import WireCommonComponents +import WireLogging import WireReusableUIComponents import WireSyncEngine diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/EmojiRepository.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/EmojiRepository.swift index 4dd013b339b..82af77df1a8 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/EmojiRepository.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/InputBar/EmojiRepository.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireUtilities protocol EmojiRepositoryInterface { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift index 6ffc850bb4b..1a51e7514cc 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift @@ -54,6 +54,10 @@ extension ConversationListViewController: ConversationListContainerViewModelDele } } + func conversationListViewControllerViewModelDidReloadContent(_ viewModel: ViewModel) { + configureEmptyPlaceholder() + } + func conversationListViewControllerViewModelRequiresUpdatingLegalHoldIndictor(_ viewModel: ViewModel) { if mainSplitViewState == .collapsed { setupLeftNavigationBarButtonItems() @@ -177,7 +181,7 @@ extension ConversationListViewController: ConversationListContainerViewModelDele withConfiguration: symbolConfiguration )! - var selectedFilterImage: UIImage = switch listContentController.listViewModel.selectedFilter { + let selectedFilterImage: UIImage = switch listContentController.listViewModel.selectedFilter { case .favorites, .groups, .oneOnOne, .folder: filledFilterImage case .none: @@ -337,7 +341,9 @@ extension ConversationListViewController: ConversationListContainerViewModelDele @objc private func presentProfile() { Task { - let selfProfileUI = UINavigationController(rootViewController: selfProfileViewControllerBuilder.build()) + let selfProfileUI = UINavigationController( + rootViewController: selfProfileViewControllerBuilder.build(mainCoordinator: mainCoordinator) + ) selfProfileUI.modalPresentationStyle = .formSheet await mainCoordinator.presentViewController(selfProfileUI) } @@ -445,7 +451,10 @@ extension ConversationListViewController: ConversationListContainerViewModelDele guard let self, let mainCoordinator else { return } Task { @MainActor [folderPickerViewControllerBuilder] in - let viewController = folderPickerViewControllerBuilder.build(mainCoordinator: mainCoordinator) + let viewController = folderPickerViewControllerBuilder.build( + mainCoordinator: mainCoordinator, + showCloseButton: true + ) if let sheet = viewController.sheetPresentationController { sheet.detents = [.medium(), .large()] sheet.prefersGrabberVisible = true diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController.swift index 01b2894d66c..2bffa542752 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController.swift @@ -332,6 +332,10 @@ final class ConversationListViewController: UIViewController { filterContainerStackView.addArrangedSubview(filterLabel) filterContainerStackView.addArrangedSubview(removeButton) + NSLayoutConstraint.activate([ + filterContainerView.heightAnchor.constraint(equalTo: filterLabel.heightAnchor, constant: 16) + ]) + // Initially hide the filter container view filterContainerView.isHidden = true } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ViewModel/ConversationListViewControllerViewModel+ConversationListContentDelegate.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ViewModel/ConversationListViewControllerViewModel+ConversationListContentDelegate.swift index 455503c171b..780a725e89e 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ViewModel/ConversationListViewControllerViewModel+ConversationListContentDelegate.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ViewModel/ConversationListViewControllerViewModel+ConversationListContentDelegate.swift @@ -35,6 +35,10 @@ extension ConversationListViewController.ViewModel: ConversationListContentDeleg ) { showActionMenu(for: conversation, from: sourceView) } + + func conversationListContentControllerDidReload(_ controller: ConversationListContentController) { + viewController?.conversationListViewControllerViewModelDidReloadContent(self) + } } extension ConversationListViewController.ViewModel { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ViewModel/ConversationListViewControllerViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ViewModel/ConversationListViewControllerViewModel.swift index d2bc7ff1ef1..8cac4e2a6d9 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ViewModel/ConversationListViewControllerViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ViewModel/ConversationListViewControllerViewModel.swift @@ -22,6 +22,7 @@ import WireAccountImageUI import WireCommonComponents import WireDataModel import WireFoundation +import WireLogging import WireMainNavigationUI import WireReusableUIComponents import WireSyncEngine @@ -58,6 +59,10 @@ protocol ConversationListContainerViewModelDelegate: AnyObject { _ viewModel: ConversationListViewController .ViewModel ) + + func conversationListViewControllerViewModelDidReloadContent( + _ viewModel: ConversationListViewController.ViewModel + ) } extension ConversationListViewController { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ConversationListContentController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ConversationListContentController.swift index 58acc6043d3..8727e320316 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ConversationListContentController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ConversationListContentController.swift @@ -74,8 +74,6 @@ final class ConversationListContentController: UICollectionViewController { flowLayout.sectionInset = .zero self.listViewModel = .init(userSession: userSession) super.init(collectionViewLayout: flowLayout) - - registerSectionHeader() } @available(*, unavailable) @@ -155,52 +153,6 @@ final class ConversationListContentController: UICollectionViewController { clearsSelectionOnViewWillAppear = false } - // MARK: - section header - - override func collectionView( - _ collectionView: UICollectionView, - viewForSupplementaryElementOfKind kind: String, - at indexPath: IndexPath - ) -> UICollectionReusableView { - - switch kind { - case UICollectionView.elementKindSectionHeader: - let section = indexPath.section - - if let header = collectionView.dequeueReusableSupplementaryView( - ofKind: kind, - withReuseIdentifier: ConversationListHeaderView.reuseIdentifier, - for: indexPath - ) as? ConversationListHeaderView { - header.title = listViewModel.sectionHeaderTitle(sectionIndex: section)?.uppercased() - - header.folderBadge = listViewModel.folderBadge(at: section) - - header.collapsed = listViewModel.collapsed(at: section) - - header.tapHandler = { [weak self] collapsed in - self?.listViewModel.setCollapsed(sectionIndex: section, collapsed: collapsed) - } - - return header - } else { - fatal("Unknown supplementary view for \(kind)") - } - default: - fatal("No supplementary view for \(kind)") - } - } - - private func registerSectionHeader() { - collectionView?.register( - ConversationListHeaderView.self, - forSupplementaryViewOfKind: - UICollectionView.elementKindSectionHeader, - withReuseIdentifier: ConversationListHeaderView.reuseIdentifier - ) - - } - /// ensures that the list selection state matches that of the model. func ensureCurrentSelection() { guard let selectedItem = listViewModel.selectedItem else { return } @@ -383,18 +335,6 @@ final class ConversationListContentController: UICollectionViewController { } extension ConversationListContentController: UICollectionViewDelegateFlowLayout { - func collectionView( - _ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - referenceSizeForHeaderInSection section: Int - ) -> CGSize { - CGSize( - width: collectionView.bounds.size.width, - height: listViewModel.sectionHeaderVisible(section: section) ? CGFloat.ConversationListSectionHeader - .height : 0 - ) - } - func collectionView( _ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, @@ -414,17 +354,6 @@ extension ConversationListContentController: UICollectionViewDelegateFlowLayout extension ConversationListContentController: ConversationListViewModelDelegate { - func listViewModel(_ model: ConversationListViewModel?, didUpdateSection section: Int) { - guard let header = collectionView.supplementaryView( - forElementKind: UICollectionView.elementKindSectionHeader, - at: IndexPath(item: 0, section: section) - ) as? ConversationListHeaderView else { - return - } - - header.folderBadge = listViewModel.folderBadge(at: section) - } - func listViewModel(_ model: ConversationListViewModel?, didSelectItem item: ConversationListItem?) { defer { scrollToMessageOnNextSelection = nil @@ -471,6 +400,7 @@ extension ConversationListContentController: ConversationListViewModelDelegate { let reloadClosure = { self.collectionView.reloadSections(IndexSet(integer: section)) self.ensureCurrentSelection() + self.contentDelegate?.conversationListContentControllerDidReload(self) } if animated { @@ -482,14 +412,13 @@ extension ConversationListContentController: ConversationListViewModelDelegate { } } - func listViewModel(_ model: ConversationListViewModel?, didChangeFolderEnabled folderEnabled: Bool) {} - func reload( using stagedChangeset: StagedChangeset, interrupt: ((Changeset) -> Bool)? = nil, setData: (C?) -> Void ) { collectionView.reload(using: stagedChangeset, interrupt: interrupt, setData: setData) + contentDelegate?.conversationListContentControllerDidReload(self) } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ViewModel/ConversationListViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ViewModel/ConversationListViewModel.swift index 5c2ebc5454b..76f3253cf77 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ViewModel/ConversationListViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ViewModel/ConversationListViewModel.swift @@ -142,10 +142,9 @@ final class ConversationListViewModel: NSObject { var kind: Kind var items: [SectionItem] - var collapsed: Bool var elements: [SectionItem] { - collapsed ? [] : items + items } /// ref to AggregateArray, we return the first found item's index @@ -166,56 +165,24 @@ final class ConversationListViewModel: NSObject { init(source: ConversationListViewModel.Section, elements: some Collection) { self.kind = source.kind - self.collapsed = source.collapsed self.items = Array(elements) } init( kind: Kind, - conversationDirectory: ConversationDirectoryType, - collapsed: Bool + conversationDirectory: ConversationDirectoryType ) { self.items = ConversationListViewModel.newList(for: kind, conversationDirectory: conversationDirectory) self.kind = kind - self.collapsed = collapsed } } static let contactRequestsItem: ConversationListConnectRequestsItem = .init() /// current selected ZMConversaton or ConversationListConnectRequestsItem object - private(set) var selectedItem: ConversationListItem? { - didSet { - /// expand the section if selcted item is update - guard let indexPath = indexPath(for: selectedItem), - collapsed(at: indexPath.section) else { return } - - setCollapsed(sectionIndex: indexPath.section, collapsed: false, batchUpdate: false) - } - } - - weak var delegate: ConversationListViewModelDelegate? { - didSet { - delegateFolderEnableState(newState: state) - } - } - - // TODO: [WPB-7307]: remove everything regarding folders - var folderEnabled: Bool { - get { - state.folderEnabled - } - - set { - guard newValue != state.folderEnabled else { return } - - state.folderEnabled = newValue + private(set) var selectedItem: ConversationListItem? - updateAllSections() - delegate?.listViewModelShouldBeReloaded() - delegateFolderEnableState(newState: state) - } - } + weak var delegate: ConversationListViewModelDelegate? // Local copies of the lists. private var sections: [Section] = [] @@ -250,40 +217,6 @@ final class ConversationListViewModel: NSObject { } } - /// for folder enabled and collapse presistent - private lazy var _state: State = { - guard isFolderStatePersistenceEnabled else { return .init() } - - guard let persistentPath = ConversationListViewModel.persistentURL, - let jsonData = try? Data(contentsOf: persistentPath) else { return State() - } - - do { - return try JSONDecoder().decode(ConversationListViewModel.State.self, from: jsonData) - } catch { - log.error("restore state error: \(error)") - return State() - } - }() - - private var state: State { - get { - _state - } - - set { - /// simulate willSet - - /// assign - if newValue != _state { - _state = newValue - } - - /// simulate didSet - saveState(state: _state) - } - } - private var conversationDirectoryToken: Any? let userSession: UserSession? @@ -297,10 +230,6 @@ final class ConversationListViewModel: NSObject { updateAllSections() } - private func delegateFolderEnableState(newState: State) { - delegate?.listViewModel(self, didChangeFolderEnabled: folderEnabled) - } - private func setupObservers() { conversationDirectoryToken = userSession?.conversationDirectory.addObserver(self) } @@ -309,20 +238,6 @@ final class ConversationListViewModel: NSObject { kind(of: sectionIndex)?.localizedName } - /// return true if seaction header is visible. - /// For .contactRequests section it is always invisible - /// When folderEnabled == true, returns false - /// - /// - Parameter sectionIndex: section number of collection view - /// - Returns: if the section exists and visible, return true. - func sectionHeaderVisible(section: Int) -> Bool { - guard sections.indices.contains(section), - kind(of: section) != .contactRequests, - folderEnabled else { return false } - - return !sections[section].items.isEmpty - } - private func kind(of sectionIndex: Int) -> Section.Kind? { guard sections.indices.contains(sectionIndex) else { return nil } @@ -346,8 +261,7 @@ final class ConversationListViewModel: NSObject { } func numberOfItems(inSection sectionIndex: Int) -> Int { - guard sections.indices.contains(sectionIndex), - !collapsed(at: sectionIndex) else { return 0 } + guard sections.indices.contains(sectionIndex) else { return 0 } return sections[sectionIndex].elements.count } @@ -435,7 +349,7 @@ final class ConversationListViewModel: NSObject { case .oneOnOne: [.contacts, .contactRequests] case let .folder(id, _): - if let folder = conversationDirectory.allFolders.first(where: { $0.remoteIdentifier == id }) { + if let folder = conversationDirectory.nonDeletedFolders.first(where: { $0.remoteIdentifier == id }) { [.folder(label: folder)] } else { // FIXME: [WPB-13905] Log invalid state once WPB-13905 is implemented @@ -448,8 +362,7 @@ final class ConversationListViewModel: NSObject { let sections = kinds.map { kind in Section( kind: kind, - conversationDirectory: conversationDirectory, - collapsed: state.collapsed.contains(kind.identifier) + conversationDirectory: conversationDirectory ) } let filterUseCase = FilterConversationsUseCase(conversationContainers: sections) @@ -475,7 +388,7 @@ final class ConversationListViewModel: NSObject { newValue[sectionNumber].items = newList - // Refresh the section header(since it may be hidden if the sectio is empty) when a section becomes + // Refresh the section header(since it may be hidden if the section is empty) when a section becomes // empty/from empty to non-empty if sections[sectionNumber].items.isEmpty || newList.isEmpty { sections = newValue @@ -498,15 +411,6 @@ final class ConversationListViewModel: NSObject { } }) } - - if let kind, - let sectionNumber = sectionNumber(for: kind) { - delegate?.listViewModel(self, didUpdateSection: sectionNumber) - } else { - sections.indices.forEach { - delegate?.listViewModel(self, didUpdateSection: $0) - } - } } @discardableResult @@ -538,138 +442,6 @@ final class ConversationListViewModel: NSObject { delegate?.listViewModel(self, didSelectItem: itemToSelect) } } - - // MARK: - folder badge - - func folderBadge(at sectionIndex: Int) -> Int { - sections[sectionIndex].items.filter { - let status = ($0.item as? ZMConversation)?.status - return status?.messagesRequiringAttention.isEmpty == false && - status?.showingAllMessages == true - }.count - } - - func collapsed(at sectionIndex: Int) -> Bool { - collapsed(at: sectionIndex, state: state) - } - - private func collapsed(at sectionIndex: Int, state: State) -> Bool { - guard let kind = kind(of: sectionIndex) else { return false } - - return state.collapsed.contains(kind.identifier) - } - - /// set a collpase state of a section - /// - /// - Parameters: - /// - sectionIndex: section to update - /// - collapsed: collapsed or expanded - /// - batchUpdate: true for update with difference kit comparison, false for reload the section animated - func setCollapsed( - sectionIndex: Int, - collapsed: Bool, - batchUpdate: Bool = true - ) { - guard let conversationDirectory = userSession?.conversationDirectory else { return } - guard let kind = kind(of: sectionIndex) else { return } - guard self.collapsed(at: sectionIndex) != collapsed else { return } - guard let sectionNumber = sectionNumber(for: kind) else { return } - - if collapsed { - state.collapsed.insert(kind.identifier) - } else { - state.collapsed.remove(kind.identifier) - } - - var newValue = sections - newValue[sectionNumber] = Section( - kind: kind, - conversationDirectory: conversationDirectory, - collapsed: collapsed - ) - - if batchUpdate { - let changeset = StagedChangeset(source: sections, target: newValue) - - delegate?.reload(using: changeset, interrupt: { _ in - false - }, setData: { data in - if let data { - self.sections = data - } - }) - } else { - sections = newValue - delegate?.listViewModel(self, didUpdateSectionForReload: sectionIndex, animated: true) - } - } - - // MARK: - state presistent - - // TODO: [WPB-7307]: the follow-up PR will remove anything around folders - // https://github.com/wireapp/wire-ios/pull/1466 - let isFolderStatePersistenceEnabled = false - - // TODO: [WPB-7307]: remove everything around the legacy folder view (grouped) - private struct State: Codable, Equatable { - var collapsed: Set - var folderEnabled: Bool - - init() { - self.collapsed = [] - self.folderEnabled = false - } - - var jsonString: String? { - let encoder = JSONEncoder() - encoder.outputFormatting = .sortedKeys - guard let jsonData = try? encoder.encode(self) else { return nil } - - return String(decoding: jsonData, as: UTF8.self) - } - } - - var jsonString: String? { - state.jsonString - } - - private func saveState(state: State) { - - guard isFolderStatePersistenceEnabled, - let jsonString = state.jsonString, - let persistentDirectory = ConversationListViewModel.persistentDirectory, - let directoryURL = URL.directoryURL(persistentDirectory) else { return } - - try! FileManager.default.createAndProtectDirectory(at: directoryURL) - - do { - try jsonString.write( - to: directoryURL.appendingPathComponent(ConversationListViewModel.persistentFilename), - atomically: true, - encoding: .utf8 - ) - } catch { - log.error("error writing ConversationListViewModel to \(directoryURL): \(error)") - } - } - - private static var persistentDirectory: String? { - guard let userID = ZMUser.selfUser()?.remoteIdentifier else { return nil } - - return "UI_state/\(userID)" - } - - private static var persistentFilename: String { - let className = String(describing: self) - return "\(className).json" - } - - static var persistentURL: URL? { - guard let persistentDirectory else { return nil } - - return URL.directoryURL(persistentDirectory)? - .appendingPathComponent(ConversationListViewModel.persistentFilename) - } } // MARK: - ZMUserObserving @@ -679,7 +451,10 @@ private let log = ZMSLog(tag: "ConversationListViewModel") // MARK: - ConversationDirectoryObserver extension ConversationListViewModel: ConversationDirectoryObserver { - func conversationDirectoryDidChange(_ changeInfo: ConversationDirectoryChangeInfo) { + func conversationDirectoryDidChange( + conversationDirectory: ConversationDirectoryType, + changeInfo: ConversationDirectoryChangeInfo + ) { if changeInfo.reloaded { // If the section was empty in certain cases collection view breaks down on the big amount of conversations, diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ViewModel/ConversationListViewModelDelegate.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ViewModel/ConversationListViewModelDelegate.swift index 2cdc68ea406..a1378e23bb7 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ViewModel/ConversationListViewModelDelegate.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentController/ViewModel/ConversationListViewModelDelegate.swift @@ -34,10 +34,6 @@ protocol ConversationListViewModelDelegate: AnyObject { func listViewModel(_ model: ConversationListViewModel?, didUpdateSectionForReload section: Int, animated: Bool) - func listViewModel(_ model: ConversationListViewModel?, didChangeFolderEnabled folderEnabled: Bool) - - func listViewModel(_ model: ConversationListViewModel?, didUpdateSection section: Int) - func reload( using stagedChangeset: StagedChangeset, interrupt: ((Changeset) -> Bool)?, diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentDelegate.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentDelegate.swift index 89f973baccd..acbe4e249c6 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentDelegate.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/ListContent/ConversationListContentDelegate.swift @@ -19,14 +19,20 @@ import WireDataModel protocol ConversationListContentDelegate: AnyObject { + func conversationList( _ controller: ConversationListContentController?, didSelect conversation: ZMConversation?, focusOnView focus: Bool ) + func conversationListContentController( _ controller: ConversationListContentController?, wantsActionMenuFor conversation: ZMConversation?, fromSourceView sourceView: UIView? ) + + func conversationListContentControllerDidReload( + _ controller: ConversationListContentController + ) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/CustomAppLock/Setup/PasscodeSetupViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/CustomAppLock/Setup/PasscodeSetupViewController.swift index 2345431d778..1cce1206a22 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/CustomAppLock/Setup/PasscodeSetupViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/CustomAppLock/Setup/PasscodeSetupViewController.swift @@ -26,8 +26,6 @@ protocol PasscodeSetupUserInterface: AnyObject { func setValidationLabelsState(errorReason: PasscodeError, passed: Bool) } -// TODO: [WPB-11836] fix issues with large font sizes (content is not scrolling) - final class PasscodeSetupViewController: UIViewController { enum Context { @@ -97,14 +95,21 @@ final class PasscodeSetupViewController: UIViewController { private let useCompactLayout: Bool private lazy var infoLabel: UILabel = { - let style = DownStyle.infoLabelStyle(compact: useCompactLayout) - let label = UILabel() - label.configMultipleLineLabel() - label.attributedText = .markdown(from: context.infoLabelString, style: style) + let label = DynamicFontLabel( + fontSpec: .normalRegularFont, + color: ColorTheme.Backgrounds.onSurfaceVariant + ) label.textAlignment = .center + label.configMultipleLineLabel() return label }() + private lazy var scrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.alwaysBounceVertical = true + return scrollView + }() + private let validationLabels: [PasscodeError: UILabel] = PasscodeError .allCases .reduce(into: [:]) { partialResult, errorReason in @@ -142,12 +147,22 @@ final class PasscodeSetupViewController: UIViewController { setupViews() } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + passcodeTextField.becomeFirstResponder() + } + + // MARK: - setup views + private func setupViews() { view.backgroundColor = SemanticColors.View.backgroundDefault - view.addSubview(contentView) + setupScrollView() + scrollView.addSubview(contentView) stackView.distribution = .fill + infoLabel.text = context.infoLabelString contentView.addSubview(stackView) @@ -179,29 +194,44 @@ final class PasscodeSetupViewController: UIViewController { createConstraints() } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) + private func setupScrollView() { + scrollView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(scrollView) - passcodeTextField.becomeFirstResponder() + let frameLayoutGuide = scrollView.frameLayoutGuide + + NSLayoutConstraint.activate([ + frameLayoutGuide.leadingAnchor.constraint(equalTo: view.leadingAnchor), + frameLayoutGuide.trailingAnchor.constraint(equalTo: view.trailingAnchor), + frameLayoutGuide.topAnchor.constraint(equalTo: view.topAnchor), + frameLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) } private func createConstraints() { [contentView, stackView].forEach { $0.translatesAutoresizingMaskIntoConstraints = false } - let widthConstraint = contentView.createContentWidthConstraint() + let heightConstraint = contentView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.heightAnchor) + let contentLayoutGuide = scrollView.contentLayoutGuide let contentPadding: CGFloat = 24 NSLayoutConstraint.activate([ // content view - widthConstraint, + heightConstraint, contentView.widthAnchor.constraint(lessThanOrEqualToConstant: 375), - contentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - contentView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), - contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor), - contentView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: contentPadding), - contentView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -contentPadding), + contentView.topAnchor.constraint(equalTo: contentLayoutGuide.topAnchor), + contentView.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor), + contentView.leadingAnchor.constraint( + greaterThanOrEqualTo: contentLayoutGuide.leadingAnchor, + constant: contentPadding + ), + contentView.trailingAnchor.constraint( + lessThanOrEqualTo: contentLayoutGuide.trailingAnchor, + constant: -contentPadding + ), + contentView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor), // stack view stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), @@ -342,21 +372,3 @@ extension PasscodeSetupViewController: UIAdaptivePresentationControllerDelegate } } } - -private extension DownStyle { - - static func infoLabelStyle(compact: Bool) -> DownStyle { - let style = DownStyle() - style.baseFont = compact ? FontSpec.smallRegularFont.font! : FontSpec.normalRegularFont.font! - style.baseFontColor = SemanticColors.Label.textDefault - - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.minimumLineHeight = compact ? 14 : 20 - paragraphStyle.maximumLineHeight = compact ? 14 : 20 - paragraphStyle.paragraphSpacing = compact ? 14 : 20 - style.baseParagraphStyle = paragraphStyle - - return style - } - -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/DeviceDetailsViewActionsHandler.swift b/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/DeviceDetailsViewActionsHandler.swift index 3052cdf7fee..99e05865ab3 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/DeviceDetailsViewActionsHandler.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/DeviceDetailsViewActionsHandler.swift @@ -18,6 +18,7 @@ import UIKit import WireDataModel +import WireLogging import WireSyncEngine final class DeviceDetailsViewActionsHandler: DeviceDetailsViewActions, ObservableObject { @@ -146,29 +147,6 @@ final class DeviceDetailsViewActionsHandler: DeviceDetailsViewActions, Observabl authenticate: oauthUseCase.invoke ) } - - @MainActor - private func fetchE2eIdentityCertificate() async throws -> E2eIdentityCertificate? { - guard let mlsClientID = MLSClientID(userClient: userClient), - let mlsGroupId = await fetchSelfConversationMLSGroupID() else { - logger.error("MLSGroupID for self was not found") - return nil - } - return try await userSession.getE2eIdentityCertificates.invoke( - mlsGroupId: mlsGroupId, - clientIds: [mlsClientID] - ).first - } - - @MainActor - private func fetchSelfConversationMLSGroupID() async -> MLSGroupID? { - await contextProvider.syncContext.perform { [weak self] in - guard let self else { - return nil - } - return ZMConversation.fetchSelfMLSConversation(in: contextProvider.syncContext)?.mlsGroupID - } - } } extension DeviceDetailsViewActionsHandler: ClientRemovalObserverDelegate { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/DeviceInfoViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/DeviceInfoViewModel.swift index 9dc84bf1cc7..f4e87153b82 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/DeviceInfoViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/DeviceInfoViewModel.swift @@ -64,9 +64,7 @@ final class DeviceInfoViewModel: ObservableObject { } var mlsThumbprint: String? { - e2eIdentityCertificate? - .mlsThumbprint - .splitStringIntoLines(charactersPerLine: 16) + userClient.mlsThumbPrint?.splitStringIntoLines(charactersPerLine: 16) } var serialNumber: String? { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/Views/DeviceDetailsView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/Views/DeviceDetailsView.swift index 29bb5190a72..552dbd8b241 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/Views/DeviceDetailsView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/Views/DeviceDetailsView.swift @@ -80,10 +80,10 @@ struct DeviceDetailsView: View { var body: some View { ScrollView { + if let thumbprint = viewModel.mlsThumbprint, !thumbprint.isEmpty { + mlsView + } if viewModel.isE2eIdentityEnabled { - if let thumbprint = viewModel.mlsThumbprint, !thumbprint.isEmpty { - mlsView - } e2eIdentityCertificateView } proteusView diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/E2EIdentity/E2EINotificationActionsHandler.swift b/wire-ios/Wire-iOS/Sources/UserInterface/E2EIdentity/E2EINotificationActionsHandler.swift index c5ea35b24fb..c67972febb8 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/E2EIdentity/E2EINotificationActionsHandler.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/E2EIdentity/E2EINotificationActionsHandler.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireSyncEngine import WireSystem diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/E2EIdentity/OAuthUseCase.swift b/wire-ios/Wire-iOS/Sources/UserInterface/E2EIdentity/OAuthUseCase.swift index 48689744e4f..fc8b6adc3a9 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/E2EIdentity/OAuthUseCase.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/E2EIdentity/OAuthUseCase.swift @@ -18,6 +18,7 @@ import AppAuth import Foundation +import WireLogging import WireRequestStrategy import WireSystem import WireUtilities diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Folders/FolderPickerViewControllerBuilder.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Folders/FolderPickerViewControllerBuilder.swift index 94879e365f7..6b2e3ca45a5 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Folders/FolderPickerViewControllerBuilder.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Folders/FolderPickerViewControllerBuilder.swift @@ -30,8 +30,8 @@ struct FolderPickerViewControllerBuilder { } @MainActor - func build(mainCoordinator: AnyMainCoordinator) -> UIViewController { - let folders: [FolderPickerOption] = conversationDirectory.allFolders.compactMap { + func build(mainCoordinator: AnyMainCoordinator, showCloseButton: Bool) -> UIViewController { + let folders: [FolderPickerOption] = conversationDirectory.nonDeletedFolders.compactMap { guard let id = $0.remoteIdentifier, let title = $0.name else { return nil } return FolderPickerOption(id: id, title: title) @@ -54,7 +54,7 @@ struct FolderPickerViewControllerBuilder { let navigationStack = NavigationStack { FolderPicker( - showCloseButton: false, + showCloseButton: showCloseButton, options: folders, helpLink: WireURLs.shared.howToAddConversationToCustomFolder, selected: selected diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Folders/Mappers/WireFolderDirectoryMapper.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Folders/Mappers/WireFolderDirectoryMapper.swift index cc80f2eb8c1..4ed26ef5151 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Folders/Mappers/WireFolderDirectoryMapper.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Folders/Mappers/WireFolderDirectoryMapper.swift @@ -27,7 +27,7 @@ struct WireFolderDirectoryMapper: FolderDirectoryTypeProtocol { } var allFolders: [Folder] { - directory.allFolders.map { label -> Folder in + directory.nonDeletedFolders.map { label -> Folder in Folder(identifier: label.remoteIdentifier, name: label.name ?? "") } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/GroupDetailsViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/GroupDetailsViewController.swift index 46b9b8ea615..276c5ddeb97 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/GroupDetailsViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/GroupDetailsViewController.swift @@ -18,6 +18,7 @@ import UIKit import WireDesign +import WireLogging import WireMainNavigationUI import WireSyncEngine @@ -134,6 +135,7 @@ final class GroupDetailsViewController: UIViewController, ZMConversationObserver }, accessibilityLabel: L10n.Accessibility.ConversationDetails.CloseButton.description) navigationItem.backBarButtonItem?.accessibilityLabel = L10n.Accessibility.Profile.BackButton.description + navigationItem.backButtonDisplayMode = .minimal } override func viewDidLoad() { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/GroupParticipantsDetail/GroupParticipantsDetailViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/GroupParticipantsDetail/GroupParticipantsDetailViewModel.swift index 507d5226963..68571876ef7 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/GroupParticipantsDetail/GroupParticipantsDetailViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/GroupParticipantsDetail/GroupParticipantsDetailViewModel.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging import WireSyncEngine private extension String { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/Sections/GroupOptionsSectionController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/Sections/GroupOptionsSectionController.swift index 8de44f95178..cededed3433 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/Sections/GroupOptionsSectionController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/Sections/GroupOptionsSectionController.swift @@ -42,7 +42,7 @@ final class GroupOptionsSectionController: GroupDetailsSectionController { switch self { case .notifications: user.canModifyNotificationSettings(in: conversation) case .guests: user.canModifyAccessControlSettings(in: conversation) - case .services: user.canModifyAccessControlSettings(in: conversation) + case .services: user.canModifyAccessControlSettings(in: conversation) && conversation.botCanBeAdded case .timeout: user.canModifyEphemeralSettings(in: conversation) } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/Sections/ParticipantsSectionController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/Sections/ParticipantsSectionController.swift index 268b2ce8e30..6025543c740 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/Sections/ParticipantsSectionController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/Sections/ParticipantsSectionController.swift @@ -18,6 +18,7 @@ import UIKit import WireDataModel +import WireLogging import WireSyncEngine enum ParticipantsRowType { @@ -186,6 +187,7 @@ final class ParticipantsSectionController: GroupDetailsSectionController { private var viewModel: ParticipantsSectionViewModel private let conversation: GroupDetailsConversationType private var token: NSObjectProtocol? + private lazy var sizingFooter = SectionFooter() init( participants: [UserType], @@ -266,8 +268,14 @@ final class ParticipantsSectionController: GroupDetailsSectionController { switch configuration { case let .user(user): guard let cell = cell as? UserCell else { return unexpectedCellHandler() } - let isE2EICertified = if let userID = user.remoteIdentifier, - let userStatus = viewModel.userStatuses[userID] { userStatus.isE2EICertified } else { false } + + let isE2EICertified = + if let userID = user.remoteIdentifier, let userStatus = viewModel.userStatuses[userID] { + userStatus.isE2EICertified + } else { + false + } + cell.configure( user: user, isE2EICertified: isE2EICertified, @@ -295,17 +303,12 @@ final class ParticipantsSectionController: GroupDetailsSectionController { referenceSizeForFooterInSection section: Int ) -> CGSize { - guard - viewModel.footerVisible, - let footer = collectionView.dequeueFooter(for: IndexPath(item: 0, section: section)) as? SectionFooter - else { - return .zero - } + guard viewModel.footerVisible else { return .zero } - footer.titleLabel.text = viewModel.footerTitle - footer.size(fittingWidth: collectionView.bounds.width) + sizingFooter.titleLabel.text = viewModel.footerTitle + sizingFooter.size(fittingWidth: collectionView.bounds.width) - return footer.bounds.size + return sizingFooter.bounds.size } override func collectionView( diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ConversationFilterSelector.swift b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ConversationFilterSelector.swift new file mode 100644 index 00000000000..a1d36194e4f --- /dev/null +++ b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ConversationFilterSelector.swift @@ -0,0 +1,62 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireDataModel + +/// Updates `conversationFilter` based on system events rather than user selection. +/// +/// For example, if the current filter is a folder filter, and the folder is deleted, this selector will remove +/// the filter. + +@MainActor +final class ConversationFilterSelector: @preconcurrency ConversationDirectoryObserver { + + private let conversationFilter: () -> ConversationFilter? + private let updateConversationFilter: (ConversationFilter?) -> Void + private var observation: Any? + + /// Creates a new ConversationFilterSelector. + /// + /// - Parameters: + /// - mainCoordinator: The main coordinator. + /// - conversationFilter: A closure that returns the current conversation filter. + + init( + conversationFilter: @escaping () -> ConversationFilter?, + updateConversationFilter: @escaping (ConversationFilter?) -> Void + ) { + self.conversationFilter = conversationFilter + self.updateConversationFilter = updateConversationFilter + } + + /// Start observing `conversationDirectory`. + + func observe(conversationDirectory: any ConversationDirectoryType) { + observation = conversationDirectory.addObserver(self) + } + + func conversationDirectoryDidChange( + conversationDirectory: ConversationDirectoryType, + changeInfo: ConversationDirectoryChangeInfo + ) { + if case let .folder(id, _) = conversationFilter(), + conversationDirectory.nonDeletedFolders.first(where: { $0.remoteIdentifier == id }) == nil { + updateConversationFilter(nil) + } + } +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/MainController/SidebarViewControllerDelegate.swift b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/SidebarViewControllerDelegate.swift index f2da52bdebb..161d67cd738 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/MainController/SidebarViewControllerDelegate.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/SidebarViewControllerDelegate.swift @@ -42,7 +42,9 @@ final class SidebarViewControllerDelegate: WireSidebarUI.SidebarViewControllerDe @MainActor public func sidebarViewControllerDidSelectAccountImage(_ viewController: SidebarViewController) { Task { - let selfProfileUI = UINavigationController(rootViewController: selfProfileUIBuilder.build()) + let selfProfileUI = UINavigationController( + rootViewController: selfProfileUIBuilder.build(mainCoordinator: mainCoordinator) + ) selfProfileUI.modalPresentationStyle = .formSheet await mainCoordinator.presentViewController(selfProfileUI) } @@ -51,7 +53,10 @@ final class SidebarViewControllerDelegate: WireSidebarUI.SidebarViewControllerDe @MainActor func sidebarViewController(_ viewController: SidebarViewController, didTapFoldersMenuItem frame: CGRect) { Task { - let folderPicker = folderPickerViewControllerBuilder.build(mainCoordinator: mainCoordinator) + let folderPicker = folderPickerViewControllerBuilder.build( + mainCoordinator: mainCoordinator, + showCloseButton: false + ) folderPicker.modalPresentationStyle = .popover if let popover = folderPicker.popoverPresentationController, diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController+StartUIDelegate.swift b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController+StartUIDelegate.swift index 48fabdc2628..4f3033055e8 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController+StartUIDelegate.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController+StartUIDelegate.swift @@ -17,6 +17,7 @@ // import WireDataModel +import WireLogging extension ZClientViewController: StartUIDelegate { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift index 8ea2238f819..b990059a51a 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift @@ -23,6 +23,7 @@ import WireAccountImageUI import WireCommonComponents import WireDesign import WireFoundation +import WireLogging import WireMainNavigationUI import WireSidebarUI import WireSyncEngine @@ -46,6 +47,15 @@ final class ZClientViewController: UIViewController { private(set) var conversationRootViewController: UIViewController? + private lazy var conversationFilterSelector = ConversationFilterSelector( + conversationFilter: { [weak conversationListViewController] in + conversationListViewController?.conversationFilter + }, + updateConversationFilter: { [weak mainCoordinator] filter in + mainCoordinator?.applyConversationFilter(filter) + } + ) + var currentConversation: ZMConversation? { conversationListViewController.selectedConversation } @@ -266,14 +276,7 @@ final class ZClientViewController: UIViewController { super.viewDidAppear(animated) firstTimeRequestToEnableAnalytics() - - // in expanded layout we want to see the same background color of the - // sidebar also for the status bar - if mainSplitViewController.isCollapsed { - view.backgroundColor = ColorTheme.Backgrounds.surface - } else { - view.backgroundColor = SidebarViewDesign().backgroundColor - } + view.backgroundColor = ColorTheme.Backgrounds.surface } private func firstTimeRequestToEnableAnalytics() { @@ -292,8 +295,6 @@ final class ZClientViewController: UIViewController { mainSplitViewController.borderColor = ColorTheme.Strokes.outline mainSplitViewController.conversationListUI = conversationListViewController - selfProfileViewControllerBuilder.mainCoordinator = .init(mainCoordinator: mainCoordinator) - settingsViewControllerBuilder.settingsPropertyFactoryDelegate = defaultSettingsPropertyFactoryDelegate mainTabBarController.archiveUI = archiveUI mainTabBarController.settingsUI = settingsViewControllerBuilder @@ -329,6 +330,8 @@ final class ZClientViewController: UIViewController { await updateCachedAccountImage() await updateCachedAccountInfo() } + + conversationFilterSelector.observe(conversationDirectory: userSession.conversationDirectory) } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { @@ -521,6 +524,15 @@ final class ZClientViewController: UIViewController { func setTopOverlay(to viewController: UIViewController?, animated: Bool = true) { topOverlayViewController?.willMove(toParent: nil) + func setupConstraints(for view: UIView, in superview: UIView) { + NSLayoutConstraint.activate([ + view.leadingAnchor.constraint(equalTo: superview.leadingAnchor), + view.topAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.topAnchor), + superview.trailingAnchor.constraint(equalTo: view.trailingAnchor), + superview.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + } + if let previousViewController = topOverlayViewController, let viewController { addChild(viewController) viewController.view.frame = topOverlayContainer.bounds @@ -532,7 +544,7 @@ final class ZClientViewController: UIViewController { to: viewController, duration: 0.5, options: .transitionCrossDissolve, - animations: { viewController.view.fitIn(view: self.view) }, + animations: { setupConstraints(for: viewController.view, in: self.view) }, completion: { _ in viewController.didMove(toParent: self) previousViewController.removeFromParent() @@ -541,14 +553,13 @@ final class ZClientViewController: UIViewController { ) } else { topOverlayContainer.addSubview(viewController.view) - viewController.view.fitIn(view: topOverlayContainer) + setupConstraints(for: viewController.view, in: topOverlayContainer) viewController.didMove(toParent: self) topOverlayViewController = viewController } } else if let previousViewController = topOverlayViewController { if animated { let heightConstraint = topOverlayContainer.heightAnchor.constraint(equalToConstant: 0) - UIView.animate( withDuration: 0.35, delay: 0, @@ -577,7 +588,7 @@ final class ZClientViewController: UIViewController { viewController.view.frame = topOverlayContainer.bounds viewController.view.translatesAutoresizingMaskIntoConstraints = false topOverlayContainer.addSubview(viewController.view) - viewController.view.fitIn(view: topOverlayContainer) + setupConstraints(for: viewController.view, in: topOverlayContainer) viewController.didMove(toParent: self) @@ -621,7 +632,7 @@ final class ZClientViewController: UIViewController { NSLayoutConstraint.activate([ topOverlayContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor), - topOverlayContainer.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + topOverlayContainer.topAnchor.constraint(equalTo: view.topAnchor), topOverlayContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor), mainSplitViewController.view.topAnchor.constraint(equalTo: topOverlayContainer.bottomAnchor), mainSplitViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), @@ -636,12 +647,7 @@ final class ZClientViewController: UIViewController { override func viewWillTransition(to size: CGSize, with coordinator: any UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) - - if mainSplitViewController.isCollapsed { - view.backgroundColor = ColorTheme.Backgrounds.surface - } else { - view.backgroundColor = SidebarViewDesign().backgroundColor - } + view.backgroundColor = ColorTheme.Backgrounds.surface } /// Open the user client list screen diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/PermissionDeniedHint/PermissionDeniedViewController+addressBook.swift b/wire-ios/Wire-iOS/Sources/UserInterface/PermissionDeniedHint/PermissionDeniedViewController+addressBook.swift deleted file mode 100644 index 67e5e9f88b6..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/PermissionDeniedHint/PermissionDeniedViewController+addressBook.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit -import WireCommonComponents -import WireDesign - -extension PermissionDeniedViewController { - - class func addressBookAccessDeniedViewController() -> PermissionDeniedViewController { - // MARK: - Properties - - typealias RegistrationAddressBookDenied = L10n.Localizable.Registration.AddressBookAccessDenied - let vc = PermissionDeniedViewController() - let title = RegistrationAddressBookDenied.Hero.title - let paragraph1 = RegistrationAddressBookDenied.Hero.paragraph1 - let paragraph2 = RegistrationAddressBookDenied.Hero.paragraph2 - - let text = [title, paragraph1, paragraph2].joined(separator: "\u{2029}") - - let attributedText = text.withCustomParagraphSpacing() - - attributedText.addAttributes([ - NSAttributedString.Key.font: FontSpec.largeThinFont.font! - ], range: (text as NSString).range(of: [paragraph1, paragraph2].joined(separator: "\u{2029}"))) - attributedText.addAttributes([ - NSAttributedString.Key.font: FontSpec.largeSemiboldFont.font! - ], range: (text as NSString).range(of: title)) - vc.heroLabel.attributedText = attributedText - - vc.settingsButton.setTitle(RegistrationAddressBookDenied.SettingsButton.title.capitalized, for: .normal) - - vc.laterButton.setTitle(RegistrationAddressBookDenied.MaybeLaterButton.title.capitalized, for: .normal) - - return vc - } -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/PermissionDeniedHint/PermissionDeniedViewController+notifications.swift b/wire-ios/Wire-iOS/Sources/UserInterface/PermissionDeniedHint/PermissionDeniedViewController+notifications.swift index 45dbdaa6393..7f5b98c9b6c 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/PermissionDeniedHint/PermissionDeniedViewController+notifications.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/PermissionDeniedHint/PermissionDeniedViewController+notifications.swift @@ -50,3 +50,16 @@ extension PermissionDeniedViewController { return vc } } + +private extension String { + func withCustomParagraphSpacing() -> NSMutableAttributedString { + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.paragraphSpacing = 10 + + return .init( + string: self, + attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle] + ) + } +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/ProfileHeaderViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/ProfileHeaderViewController.swift index 86ed1bb456e..54e57159b36 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/ProfileHeaderViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/ProfileHeaderViewController.swift @@ -20,6 +20,7 @@ import SwiftUI import UIKit import WireCommonComponents import WireDesign +import WireLogging import WireSyncEngine final class ProfileHeaderViewController: UIViewController { @@ -409,6 +410,7 @@ final class ProfileHeaderViewController: UIViewController { let qrCodeView = QRCodeView(viewModel: viewModel) let hostingController = UIHostingController(rootView: qrCodeView) hostingController.setupNavigationBarTitle(L10n.Localizable.Qrcode.title) + hostingController.navigationItem.rightBarButtonItem = UIBarButtonItem.closeButton( action: UIAction { [weak self] _ in self?.presentingViewController?.dismiss(animated: true) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewController.swift index d2a0266d43b..f0ca5313798 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewController.swift @@ -20,9 +20,13 @@ // - alert that new devices have been added // - alert about read receipts enabled +import SwiftUI import UIKit +import WireAPI import WireCommonComponents import WireDesign +import WireDomainAPI +import WireIndividualToTeamMigrationUI import WireMainNavigationUI import WireReusableUIComponents import WireSettingsUI @@ -38,9 +42,12 @@ final class SelfProfileViewController: UIViewController { private let settingsController: SettingsTableViewController private weak var accountSelectorView: AccountSelectorView? - private let profileContainerView = UIView() + private let profileLayoutGuide = UILayoutGuide() + private var profileLayoutGuideViewTopConstraint = NSLayoutConstraint() + private var profileLayoutGuideBannerTopConstraint = NSLayoutConstraint() private let profileHeaderViewController: ProfileHeaderViewController private let profileImagePicker = ProfileImagePickerManager() + private var teamMigrationBanner: UIViewController? private let accountSelector: AccountSelector? let mainCoordinator: AnyMainCoordinator @@ -106,6 +113,15 @@ final class SelfProfileViewController: UIViewController { userSession.enqueue { selfUser.refreshTeamData() } + } else if + let backendInfoApiVersion = BackendInfo.apiVersion, + let apiVersion = WireAPI.APIVersion(rawValue: UInt(backendInfoApiVersion.rawValue)), + apiVersion >= .v7 { + self.teamMigrationBanner = SelfProfileViewCallToActionBannerHostingController( + actionCallback: { [weak self] action in + self?.onTeamCreationBannerInteraction(action, apiVersion: apiVersion) + } + ) } } @@ -123,8 +139,8 @@ final class SelfProfileViewController: UIViewController { profileHeaderViewController.imageView.addGestureRecognizer(tapGestureRecognizer) addChild(profileHeaderViewController) - profileContainerView.addSubview(profileHeaderViewController.view) - view.addSubview(profileContainerView) + view.addLayoutGuide(profileLayoutGuide) + view.addSubview(profileHeaderViewController.view) profileHeaderViewController.didMove(toParent: self) addChild(settingsController) @@ -133,10 +149,15 @@ final class SelfProfileViewController: UIViewController { settingsController.tableView.isScrollEnabled = false + if let teamMigrationBanner { + addChild(teamMigrationBanner) + view.addSubview(teamMigrationBanner.view) + teamMigrationBanner.didMove(toParent: self) + } + createConstraints() setupAccessibility() view.backgroundColor = SemanticColors.View.backgroundDefault - } override func viewWillAppear(_ animated: Bool) { @@ -146,6 +167,7 @@ final class SelfProfileViewController: UIViewController { self?.presentingViewController?.dismiss(animated: true) }, accessibilityLabel: L10n.Localizable.General.close) navigationController?.navigationBar.backgroundColor = SemanticColors.View.backgroundDefault + navigationItem.backButtonDisplayMode = .minimal } override func viewDidAppear(_ animated: Bool) { @@ -170,26 +192,45 @@ final class SelfProfileViewController: UIViewController { private func createConstraints() { profileHeaderViewController.view.translatesAutoresizingMaskIntoConstraints = false - profileContainerView.translatesAutoresizingMaskIntoConstraints = false settingsController.view.translatesAutoresizingMaskIntoConstraints = false + profileLayoutGuideViewTopConstraint = profileLayoutGuide.topAnchor + .constraint(equalTo: view.safeAreaLayoutGuide.topAnchor) + + if let teamMigrationBanner { + teamMigrationBanner.view.translatesAutoresizingMaskIntoConstraints = false + profileLayoutGuideBannerTopConstraint = profileLayoutGuide.topAnchor + .constraint(equalTo: teamMigrationBanner.view.bottomAnchor) + NSLayoutConstraint.activate([ + + // teamMigrationBanner + teamMigrationBanner.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + teamMigrationBanner.view.topAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.topAnchor, + constant: 20 + ), + teamMigrationBanner.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + profileLayoutGuideBannerTopConstraint + ]) + } else { + profileLayoutGuideViewTopConstraint.isActive = true + } + NSLayoutConstraint.activate([ - // profileContainerView - profileContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - profileContainerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - profileContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + // profileLayoutGuide + profileLayoutGuide.bottomAnchor.constraint(equalTo: settingsController.view.topAnchor), // profileView - profileHeaderViewController.view.leadingAnchor.constraint(equalTo: profileContainerView.leadingAnchor), - profileHeaderViewController.view.topAnchor.constraint(greaterThanOrEqualTo: profileContainerView.topAnchor), - profileHeaderViewController.view.trailingAnchor.constraint(equalTo: profileContainerView.trailingAnchor), + profileHeaderViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + profileHeaderViewController.view.topAnchor.constraint(greaterThanOrEqualTo: profileLayoutGuide.topAnchor), + profileHeaderViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), profileHeaderViewController.view.bottomAnchor - .constraint(lessThanOrEqualTo: profileContainerView.bottomAnchor), - profileHeaderViewController.view.centerYAnchor.constraint(equalTo: profileContainerView.centerYAnchor), + .constraint(lessThanOrEqualTo: profileLayoutGuide.bottomAnchor), + profileHeaderViewController.view.centerYAnchor.constraint(equalTo: profileLayoutGuide.centerYAnchor), // settingsControllerView settingsController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - settingsController.view.topAnchor.constraint(equalTo: profileContainerView.bottomAnchor), settingsController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), settingsController.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) ]) @@ -204,17 +245,73 @@ final class SelfProfileViewController: UIViewController { // MARK: - Events + private func onTeamCreationBannerInteraction( + _ action: SelfProfileViewCallToActionBanner.Action, + apiVersion: WireAPI.APIVersion + ) { + switch action { + case .createWireTeam: + let sessionContextProvider = userSession.contextProvider + let user = ZMUser.selfUser(inUserSession: sessionContextProvider) + guard let userName = user.normalizedName, + let useCase = SessionManager.shared?.activeUserSession? + .createIndividualToTeamMigrationUseCase(apiVersion: apiVersion) else { + return + } + userDidTapCreateTeam(useCase: useCase, userName: userName) + } + } + + private func userDidTapCreateTeam(useCase: IndividualToTeamMigrationUseCase, userName: String) { + let vc = IndividualToTeamMigrationViewController( + useCase: useCase, + userProfileName: userName, + actionCallback: { [weak self] action in + Task { [weak self] in + await MainActor.run { [weak self] in + guard let self else { return } + switch action { + case .cancel: + presentedViewController?.dismiss(animated: true) + case .completionGoToApp: + dismissIndividualToTeamMigrationBanner() + presentedViewController?.dismiss(animated: true) + case .completionGoToTeamManagement: + dismissIndividualToTeamMigrationBanner() + presentedViewController?.dismiss(animated: true, completion: { [weak self] in + self?.navigateToTeam() + }) + } + } + } + } + ) + present(vc, animated: true) + } + + private func dismissIndividualToTeamMigrationBanner() { + teamMigrationBanner?.willMove(toParent: nil) + teamMigrationBanner?.view.removeFromSuperview() + teamMigrationBanner?.removeFromParent() + teamMigrationBanner = nil + profileLayoutGuideBannerTopConstraint.isActive = false + profileLayoutGuideViewTopConstraint.isActive = true + } + + private func navigateToTeam() { + // TODO: [WPB-11968] navigate to team + } + @objc private func userDidTapProfileImage(_ sender: UIGestureRecognizer) { guard userRightInterfaceType.selfUserIsPermitted(to: .editProfilePicture) else { return } - let alertController = profileImagePicker.selectProfileImage() + let imageView = profileHeaderViewController.imageView + let alertController = profileImagePicker.selectProfileImage( + popoverConfiguration: .sourceView(sourceView: imageView, sourceRect: .null) + ) if let popoverPresentationController = alertController.popoverPresentationController { - popoverPresentationController.sourceView = profileHeaderViewController.imageView.superview! - popoverPresentationController.sourceRect = profileHeaderViewController.imageView.frame.insetBy( - dx: -4, - dy: -4 - ) + popoverPresentationController.sourceView = imageView } present(alertController, animated: true) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewControllerBuilder.swift b/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewControllerBuilder.swift index 07dac4fbfb5..986c401fa7a 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewControllerBuilder.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewControllerBuilder.swift @@ -26,7 +26,6 @@ final class SelfProfileViewControllerBuilder: SelfProfileViewControllerBuilderPr var selfUser: SettingsSelfUser var userRightInterfaceType: UserRightInterface.Type var userSession: UserSession - var mainCoordinator: AnyMainCoordinator! var accountSelector: AccountSelector? var trackingManager: TrackingManager? @@ -42,7 +41,7 @@ final class SelfProfileViewControllerBuilder: SelfProfileViewControllerBuilderPr self.accountSelector = accountSelector } - func build() -> UIViewController { + func build(mainCoordinator: AnyMainCoordinator) -> UIViewController { SelfProfileViewController( selfUser: selfUser, userRightInterfaceType: userRightInterfaceType, diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewControllerBuilderProtocol.swift b/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewControllerBuilderProtocol.swift index 6781e9f589c..99115a24395 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewControllerBuilderProtocol.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewControllerBuilderProtocol.swift @@ -21,5 +21,5 @@ import UIKit // sourcery: AutoMockable protocol SelfProfileViewControllerBuilderProtocol { @MainActor - func build() -> UIViewController + func build(mainCoordinator: AnyMainCoordinator) -> UIViewController } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Services/ServiceDetailViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Services/ServiceDetailViewController.swift index 57d20f70b24..27b6a102426 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Services/ServiceDetailViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Services/ServiceDetailViewController.swift @@ -20,11 +20,18 @@ import UIKit import WireDesign import WireSyncEngine -extension ConversationLike where Self: SwiftConversationLike { +extension MessageProtocol { + var supportsBots: Bool { + !isOne(of: .mls, .mixed) + } +} + +extension ConversationLike where Self: GroupDetailsConversationType { var botCanBeAdded: Bool { conversationType != .oneOnOne && teamType != nil && - allowServices + allowServices && + messageProtocol.supportsBots } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/AccentColorPicker.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/AccentColorPicker.swift index a8853b9ebf8..9fdabfb0080 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/AccentColorPicker.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/AccentColorPicker.swift @@ -16,7 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Inject import SwiftUI import WireCommonComponents import WireDesign @@ -28,19 +27,17 @@ struct AccentColorPicker: View { @State var selectedColor: AccentColor private let colorViewSize: CGFloat = 28 - @ObserveInjection var inject - let onColorSelect: ((AccentColor) -> Void)? var body: some View { accentColorList - .enableInjection() } @ViewBuilder private var accentColorList: some View { List(AccentColor.allCases, id: \.self) { color in cell(for: color) .listRowBackground(Color(SemanticColors.View.backgroundUserCell)) + .contentShape(Rectangle()) .onTapGesture { selectedColor = color onColorSelect?(color) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift index c47784c2340..f559ec8ccd5 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift @@ -17,6 +17,7 @@ // import UIKit +import WireDesign import WireReusableUIComponents final class BackupViewController: UIViewController { @@ -48,7 +49,7 @@ final class BackupViewController: UIViewController { } private func setupViews() { - view.backgroundColor = .clear + view.backgroundColor = ColorTheme.Backgrounds.background tableView.isScrollEnabled = false tableView.rowHeight = UITableView.automaticDimension diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsAppearanceCellDescriptor.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsAppearanceCellDescriptor.swift index 8b0733de30a..09ef691da41 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsAppearanceCellDescriptor.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsAppearanceCellDescriptor.swift @@ -21,14 +21,14 @@ import UIKit import WireSettingsUI import WireSyncEngine -class SettingsAppearanceCellDescriptor: SettingsCellDescriptorType, SettingsExternalScreenCellDescriptorType { +class SettingsAppearanceCellDescriptor: SettingsGroupCellDescriptorType, SettingsCellDescriptorType { static let cellType: SettingsTableCellProtocol.Type = SettingsAppearanceCell.self private var text: String private let presentationStyle: PresentationStyle weak var viewController: UIViewController? - let presentationAction: () -> (UIViewController?) + let presentationAction: (_ sender: UIView) -> UIViewController? var identifier: String? weak var group: SettingsGroupCellDescriptorType? @@ -48,7 +48,7 @@ class SettingsAppearanceCellDescriptor: SettingsCellDescriptorType, SettingsExte text: String, previewGenerator: PreviewGeneratorType? = .none, presentationStyle: PresentationStyle, - presentationAction: @escaping () -> (UIViewController?), + presentationAction: @escaping (_ sender: UIView) -> UIViewController?, settingsCoordinator: AnySettingsCoordinator ) { self.text = text @@ -81,7 +81,7 @@ class SettingsAppearanceCellDescriptor: SettingsCellDescriptorType, SettingsExte // MARK: - SettingsCellDescriptorType func select(_ value: SettingsPropertyValue, sender: UIView) { - guard let controllerToShow = generateViewController() else { return } + guard let controllerToShow = generateViewController(sender: sender) else { return } switch presentationStyle { case .alert: @@ -97,7 +97,7 @@ class SettingsAppearanceCellDescriptor: SettingsCellDescriptorType, SettingsExte } } - func generateViewController() -> UIViewController? { - presentationAction() + private func generateViewController(sender: UIView) -> UIViewController? { + presentationAction(sender) } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptor.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptor.swift index 89d51017842..143175aa55e 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptor.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptor.swift @@ -113,10 +113,6 @@ extension SettingsInternalGroupCellDescriptorType { } } -protocol SettingsExternalScreenCellDescriptorType: SettingsGroupCellDescriptorType { - var presentationAction: () -> (UIViewController?) { get } -} - protocol SettingsPropertyCellDescriptorType: SettingsCellDescriptorType { var settingsProperty: SettingsProperty { get } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 93919e2eff9..7f56b11efbe 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -304,8 +304,10 @@ extension SettingsCellDescriptorFactory { return .image(image) } - let presentationAction: () -> (UIViewController?) = { - profileImagePicker.selectProfileImage() + let presentationAction: (_ sender: UIView) -> UIViewController? = { sender in + profileImagePicker.selectProfileImage( + popoverConfiguration: .sourceView(sourceView: sender, sourceRect: .null) + ) } return SettingsAppearanceCellDescriptor( text: L10n.Localizable.Self.Settings.AccountPictureGroup.picture.capitalized, @@ -335,7 +337,7 @@ extension SettingsCellDescriptorFactory { return SettingsCellPreview.color((selfUser.accentColor ?? .default).uiColor) } - private func colorElementPresentationAction() -> UIViewController { + private func colorElementPresentationAction(sender: UIView) -> UIViewController { guard let selfUser = ZMUser.selfUser(), let userSession = ZMUserSession.shared() diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Developer.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Developer.swift index 98adddecaae..c7a63debd63 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Developer.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Developer.swift @@ -128,7 +128,7 @@ extension SettingsCellDescriptorFactory { Button( title: "Trigger resyncResources", isDestructive: false, - selectAction: DebugActions.triggerResyncResources + selectAction: { _ in DebugActions.triggerResyncResources() } ) ) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Options.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Options.swift index 87a29f63067..bdecd815636 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Options.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Options.swift @@ -28,7 +28,6 @@ extension SettingsCellDescriptorFactory { var optionsGroup: any SettingsCellDescriptorType { let descriptors = [ - shareContactsDisabledSection, clearHistorySection, notificationVisibleSection, chatHeadsSection, @@ -58,25 +57,6 @@ extension SettingsCellDescriptorFactory { // MARK: - Sections - private var shareContactsDisabledSection: SettingsSectionDescriptorType { - let settingsButton = SettingsButtonCellDescriptor( - title: L10n.Localizable.Self.Settings.PrivacyContactsMenu.SettingsButton.title, - isDestructive: false, - selectAction: { _ in - UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) - } - ) - - return SettingsSectionDescriptor( - cellDescriptors: [settingsButton], - header: L10n.Localizable.Self.Settings.PrivacyContactsSection.title, - footer: L10n.Localizable.Self.Settings.PrivacyContactsMenu.DescriptionDisabled.title, - visibilityAction: { _ in - AddressBookHelper.sharedHelper.isAddressBookAccessDisabled - } - ) - } - private var clearHistorySection: SettingsSectionDescriptorType { let clearHistoryButton = SettingsButtonCellDescriptor( title: L10n.Localizable.Self.Settings.Privacy.ClearHistory.title, diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsExternalScreenCellDescriptor.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsExternalScreenCellDescriptor.swift index 3109d74276c..d229c4a2fa6 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsExternalScreenCellDescriptor.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsExternalScreenCellDescriptor.swift @@ -33,7 +33,7 @@ enum AccessoryViewMode: Int { case alwaysHide } -class SettingsExternalScreenCellDescriptor: SettingsExternalScreenCellDescriptorType, SettingsControllerGeneratorType { +class SettingsExternalScreenCellDescriptor: SettingsGroupCellDescriptorType, SettingsControllerGeneratorType { static let cellType: SettingsTableCellProtocol.Type = SettingsTableCell.self var visible: Bool = true let title: String diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Debug Report/LogFilesProvider.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Debug Report/LogFilesProvider.swift index dd3529b7b58..9474db7be4d 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Debug Report/LogFilesProvider.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Debug Report/LogFilesProvider.swift @@ -18,6 +18,7 @@ import UIKit import WireCommonComponents +import WireLogging import WireSystem import ZipArchive diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Debug Report/SettingsDebugReportViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Debug Report/SettingsDebugReportViewModel.swift index 17b41086729..52526de690b 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Debug Report/SettingsDebugReportViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Debug Report/SettingsDebugReportViewModel.swift @@ -18,6 +18,7 @@ import MessageUI import WireCommonComponents +import WireLogging import WireSyncEngine final class SettingsDebugReportViewModel: SettingsDebugReportViewModelProtocol { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DebugActions.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DebugActions.swift index 0053b7e3960..68117d8d436 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DebugActions.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DebugActions.swift @@ -145,7 +145,7 @@ enum DebugActions { sendNext(count: 0) } - static func triggerResyncResources(_ type: any SettingsCellDescriptorType) { + static func triggerResyncResources() { ZMUserSession.shared()?.syncManagedObjectContext.performGroupedBlock { ZMUserSession.shared()?.requestResyncResources() } @@ -242,8 +242,10 @@ enum DebugActions { switch command { case .repairInvalidAccessRoles: DebugActions.updateInvalidAccessRoles() - } + case .resyncResources: + DebugActions.triggerResyncResources() + } } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DebugCommand.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DebugCommand.swift index 658fa2b30c3..ab451fcc3fb 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DebugCommand.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DebugCommand.swift @@ -20,15 +20,26 @@ import Foundation enum DebugCommand { - /// Update accessRoles for existing conversations where the team is nil and accessRoles == [.teamMember] + /// Update accessRoles for existing conversations where the + /// team is nil and accessRoles == [.teamMember] + case repairInvalidAccessRoles + /// Fetch team, users, connections, conversations, etc, from + /// the backend to update local database. + + case resyncResources + init?(string: String) { // We may want to have commands that accept arguments, which means // we'd have to do the parsing of the command here. switch string { case "repairInvalidAccessRoles": self = .repairInvalidAccessRoles + + case "resync resources": + self = .resyncResources + default: return nil } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DeviceManagement/ClientListViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DeviceManagement/ClientListViewController.swift index 0c4c3d14984..a12a557cff6 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DeviceManagement/ClientListViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/DeviceManagement/ClientListViewController.swift @@ -19,6 +19,7 @@ import SwiftUI import WireCommonComponents import WireDesign +import WireLogging import WireMainNavigationUI import WireReusableUIComponents import WireSettingsUI @@ -514,10 +515,7 @@ final class ClientListViewController: UIViewController, private func updateCertificates(for userClients: [UserClient]) async { guard let userSession, - let selfMlsGroupID = await userSession.fetchSelfConversationMLSGroupID(), - // dangerous access: ZMUserSession.e2eiFeature initialises a FeatureRepository using the viewContext, thus - // the following line must be executed o the main thread - userSession.e2eiFeature.isEnabled + let selfMlsGroupID = await userSession.fetchSelfConversationMLSGroupID() else { return } @@ -543,9 +541,11 @@ final class ClientListViewController: UIViewController, for (client, mlsClientId) in mlsClients { if let e2eiCertificate = certificates.first(where: { $0.clientId == mlsClientId.rawValue }) { - client.e2eIdentityCertificate = e2eiCertificate + if userSession.e2eiFeature.isEnabled { + client.e2eIdentityCertificate = e2eiCertificate + } + client.mlsThumbPrint = e2eiCertificate.mlsThumbprint } - client.mlsThumbPrint = client.e2eIdentityCertificate?.mlsThumbprint } } catch { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/MFMailComposeViewController+Logs.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/MFMailComposeViewController+Logs.swift index 98d1d4e95ad..cf973e39fc6 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/MFMailComposeViewController+Logs.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/MFMailComposeViewController+Logs.swift @@ -19,6 +19,7 @@ import Foundation import MessageUI import WireCommonComponents +import WireLogging import WireSystem extension MFMailComposeViewController { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/SettingsTableViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/SettingsTableViewController.swift index 870b2508e67..4a4e0c1b06b 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/SettingsTableViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/SettingsTableViewController.swift @@ -204,6 +204,7 @@ final class SettingsTableViewController: SettingsBaseTableViewController { override func viewDidLoad() { super.viewDidLoad() setupTableView() + setupNavigationBarAccessibility() } @@ -212,6 +213,10 @@ final class SettingsTableViewController: SettingsBaseTableViewController { appearance.configureWithDefaultBackground() appearance.backgroundColor = ColorTheme.Backgrounds.surface + let backIndicator = UIImage(resource: view.isRightToLeft ? .forwardArrow : .backArrow) + appearance.setBackIndicatorImage(backIndicator, transitionMaskImage: backIndicator) + navigationItem.backButtonDisplayMode = .minimal + // Configure appearance for different states navigationController?.navigationBar.standardAppearance = appearance navigationController?.navigationBar.scrollEdgeAppearance = appearance diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ShareContacts/ShareContactsViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ShareContacts/ShareContactsViewController.swift deleted file mode 100644 index fca7f91ce94..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ShareContacts/ShareContactsViewController.swift +++ /dev/null @@ -1,230 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit -import WireCommonComponents -import WireDesign - -// MARK: - String Extension - -extension String { - - func withCustomParagraphSpacing() -> NSMutableAttributedString { - - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.paragraphSpacing = 10 - - return .init( - string: self, - attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle] - ) - } -} - -// MARK: - ShareContactsViewController - -final class ShareContactsViewController: UIViewController { - - // MARK: - Properties - - typealias RegistrationShareContacts = L10n.Localizable.Registration.ShareContacts - - weak var delegate: ShareContactsViewControllerDelegate? - - private var notNowButtonHidden = false - private(set) var showingAddressBookAccessDeniedViewController = false - - private lazy var notNowButton = { - let notNowButton = ZMButton( - style: .secondaryTextButtonStyle, - cornerRadius: 16, - fontSpec: .normalSemiboldFont - ) - notNowButton.setTitle(RegistrationShareContacts.SkipButton.title.capitalized, for: .normal) - notNowButton.addTarget(self, action: #selector(shareContactsLater), for: .primaryActionTriggered) - return notNowButton - }() - - private let heroLabel: UILabel = { - let heroLabel = UILabel() - heroLabel.textColor = SemanticColors.Label.textDefault - heroLabel.numberOfLines = 0 - heroLabel.font = FontSpec.largeSemiboldFont.font! - heroLabel.attributedText = ShareContactsViewController.attributedHeroText - return heroLabel - }() - - private let shareContactsButton = { - let shareContactsButton = ZMButton( - style: .accentColorTextButtonStyle, - cornerRadius: 16, - fontSpec: .normalSemiboldFont - ) - shareContactsButton.setTitle(RegistrationShareContacts.FindFriendsButton.title.capitalized, for: .normal) - - return shareContactsButton - }() - - private let shareContactsContainerView = UIView() - private let addressBookAccessDeniedViewController = PermissionDeniedViewController - .addressBookAccessDeniedViewController() - - private static var attributedHeroText: NSAttributedString { - let title = RegistrationShareContacts.Hero.title - let paragraph = RegistrationShareContacts.Hero.paragraph - - let text = [title, paragraph].joined(separator: "\u{2029}") - - let attributedText = text.withCustomParagraphSpacing() - - attributedText.addAttributes([ - NSAttributedString.Key.foregroundColor: SemanticColors.Label.textDefault, - NSAttributedString.Key.font: FontSpec.largeThinFont.font! - ], range: (text as NSString).range(of: paragraph)) - - return attributedText - } - - // MARK: - Override methods - - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = SemanticColors.View.backgroundDefault - setupViews() - createConstraints() - - if AddressBookHelper.sharedHelper.isAddressBookAccessDisabled { - displayContactsAccessDeniedMessage(animated: false) - } - } - - // MARK: - Setup - - private func setupViews() { - - view.addSubview(shareContactsContainerView) - - shareContactsContainerView.addSubview(heroLabel) - - notNowButton.isHidden = notNowButtonHidden - shareContactsContainerView.addSubview(notNowButton) - - shareContactsButton.addTarget(self, action: #selector(shareContacts), for: .primaryActionTriggered) - - shareContactsContainerView.addSubview(shareContactsButton) - - addToSelf(addressBookAccessDeniedViewController) - addressBookAccessDeniedViewController.delegate = self - addressBookAccessDeniedViewController.view.isHidden = true - } - - private func createConstraints() { - - shareContactsContainerView.translatesAutoresizingMaskIntoConstraints = false - addressBookAccessDeniedViewController.view.translatesAutoresizingMaskIntoConstraints = false - heroLabel.translatesAutoresizingMaskIntoConstraints = false - shareContactsButton.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - shareContactsContainerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - shareContactsContainerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), - shareContactsContainerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), - shareContactsContainerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), - - addressBookAccessDeniedViewController.view.topAnchor - .constraint(equalTo: addressBookAccessDeniedViewController.view.superview!.topAnchor), - addressBookAccessDeniedViewController.view.bottomAnchor - .constraint(equalTo: addressBookAccessDeniedViewController.view.superview!.bottomAnchor), - addressBookAccessDeniedViewController.view.leadingAnchor - .constraint(equalTo: addressBookAccessDeniedViewController.view.superview!.leadingAnchor), - addressBookAccessDeniedViewController.view.trailingAnchor - .constraint(equalTo: addressBookAccessDeniedViewController.view.superview!.trailingAnchor), - - heroLabel.leadingAnchor.constraint(equalTo: heroLabel.superview!.leadingAnchor, constant: 28), - heroLabel.trailingAnchor.constraint(equalTo: heroLabel.superview!.trailingAnchor, constant: -28), - - shareContactsButton.topAnchor.constraint(equalTo: heroLabel.bottomAnchor, constant: 24), - shareContactsButton.heightAnchor.constraint(equalToConstant: 56), - - shareContactsButton.bottomAnchor.constraint( - equalTo: shareContactsButton.superview!.bottomAnchor, - constant: -28 - ), - shareContactsButton.leadingAnchor.constraint( - equalTo: shareContactsButton.superview!.leadingAnchor, - constant: 28 - ), - shareContactsButton.trailingAnchor.constraint( - equalTo: shareContactsButton.superview!.trailingAnchor, - constant: -28 - ) - ]) - } - - // MARK: - Actions - - @objc - private func shareContacts() { - AddressBookHelper.sharedHelper.requestPermissions { [weak self] success in - guard let self else { return } - if success { - delegate?.shareContactsViewControllerDidFinish(self) - } else { - displayContactsAccessDeniedMessage(animated: true) - } - } - } - - @objc - private func shareContactsLater() { - delegate?.shareContactsViewControllerDidSkip(self) - } - - // MARK: - AddressBook Access Denied ViewController - - func displayContactsAccessDeniedMessage(animated: Bool) { - view.window?.endEditing(true) - - showingAddressBookAccessDeniedViewController = true - - if animated { - UIView.transition( - from: shareContactsContainerView, - to: addressBookAccessDeniedViewController.view, - duration: 0.35, - options: [.showHideTransitionViews, .transitionCrossDissolve] - ) - } else { - shareContactsContainerView.isHidden = true - addressBookAccessDeniedViewController.view.isHidden = false - } - } -} - -// MARK: - ShareContactsViewController Extension - -extension ShareContactsViewController: PermissionDeniedViewControllerDelegate { - - func permissionDeniedViewControllerDidSkip(_ viewController: PermissionDeniedViewController) { - delegate?.shareContactsViewControllerDidSkip(self) - } - - func permissionDeniedViewControllerDidOpenNotificationSettings(_ viewController: PermissionDeniedViewController) { - // no op - } -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ShareContacts/ShareContactsViewControllerDelegate.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ShareContacts/ShareContactsViewControllerDelegate.swift deleted file mode 100644 index 1224ea0c54e..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ShareContacts/ShareContactsViewControllerDelegate.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -protocol ShareContactsViewControllerDelegate: AnyObject { - func shareContactsViewControllerDidSkip(_ viewController: ShareContactsViewController) - func shareContactsViewControllerDidFinish(_ viewController: ShareContactsViewController) -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsView.swift index af0e74ef3a2..76c58af8e08 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsView.swift @@ -104,31 +104,6 @@ final class SearchResultsView: UIView { super.layoutSubviews() } - var accessoryView: UIView? { - didSet { - guard oldValue != accessoryView else { return } - - oldValue?.removeFromSuperview() - - if let accessoryView { - accessoryContainer.addSubview(accessoryView) - accessoryView.translatesAutoresizingMaskIntoConstraints = false - accessoryContainerHeightConstraint?.isActive = false - - NSLayoutConstraint.activate([ - accessoryView.leadingAnchor.constraint(equalTo: accessoryContainer.leadingAnchor), - accessoryView.topAnchor.constraint(equalTo: accessoryContainer.topAnchor), - accessoryView.trailingAnchor.constraint(equalTo: accessoryContainer.trailingAnchor), - accessoryView.bottomAnchor.constraint(equalTo: accessoryContainer.bottomAnchor) - ]) - } else { - accessoryContainerHeightConstraint?.isActive = true - } - - updateContentInset() - } - } - var emptyResultView: UIView? { didSet { guard oldValue != emptyResultView else { return } @@ -174,26 +149,8 @@ final class SearchResultsView: UIView { } private func updateContentInset() { - - if let accessoryView { - accessoryView.layoutIfNeeded() - - // Use the safeAreaInsets of the window or screen directly to determine if there's a notch - if let window = UIApplication.shared.windows.first { - let safeAreaInsets = window.safeAreaInsets - let bottomInset = (safeAreaInsets.bottom > 0 ? accessoryViewMargin : 0) + accessoryView.frame - .height - safeAreaInsets.bottom - - // Add padding at the bottom of the screen - collectionView.contentInset.bottom = bottomInset - collectionView.horizontalScrollIndicatorInsets.bottom = bottomInset - collectionView.verticalScrollIndicatorInsets.bottom = bottomInset - } - } else { - // Reset the insets if no accessory view is available - collectionView.contentInset.bottom = 0 - collectionView.horizontalScrollIndicatorInsets.bottom = 0 - collectionView.verticalScrollIndicatorInsets.bottom = 0 - } + collectionView.contentInset.bottom = 0 + collectionView.horizontalScrollIndicatorInsets.bottom = 0 + collectionView.verticalScrollIndicatorInsets.bottom = 0 } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift index ab1f4fc81c5..54a1be98479 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift @@ -17,6 +17,7 @@ // import UIKit +import WireLogging import WireSyncEngine enum SearchGroup: Int { @@ -145,16 +146,10 @@ final class SearchResultsViewController: UIViewController { return view }() - private lazy var searchDirectory: SearchDirectory? = { - guard let session = userSession as? ZMUserSession else { - return nil - } - - return SearchDirectory(userSession: session) - }() - - let userSelection: UserSelection - let userSession: UserSession + private let userSelection: UserSelection + private let userSession: UserSession + private let searchUsersUseCase: SearchUsersUseCaseProtocol + private var pendingSearchTask: Task? let sectionController: SectionCollectionViewController = .init() let contactsSection: ContactsSectionController = .init() @@ -173,7 +168,6 @@ final class SearchResultsViewController: UIViewController { let servicesSection: SearchServicesSectionController let inviteTeamMemberSection: InviteTeamMemberSection - var pendingSearchTask: SearchTask? var isAddingParticipants: Bool let isFederationEnabled: Bool var searchGroup: SearchGroup = .people { @@ -193,10 +187,6 @@ final class SearchResultsViewController: UIViewController { } } - deinit { - searchDirectory?.tearDown() - } - init( userSelection: UserSelection, userSession: UserSession, @@ -210,6 +200,7 @@ final class SearchResultsViewController: UIViewController { self.mode = .list self.shouldIncludeGuests = shouldIncludeGuests self.isFederationEnabled = isFederationEnabled + self.searchUsersUseCase = userSession.makeSearchUsersUseCase() let team = userSession.selfUser.membership?.team let teamName = team?.name @@ -264,37 +255,31 @@ final class SearchResultsViewController: UIViewController { updateVisibleSections() searchResultsView.emptyResultContainer.isHidden = !isResultEmpty - searchDirectory?.updateIncompleteMetadataIfNeeded() } - @objc - func cancelPreviousSearch() { + private func performSearch( + query: String, + options: SearchOptions + ) { pendingSearchTask?.cancel() pendingSearchTask = nil - } - - private func performSearch(query: String, options: SearchOptions) { - let selfUser = userSession.selfUser - - pendingSearchTask?.cancel() searchResultsView.emptyResultContainer.isHidden = true - var options = options - options.updateForSelfUserTeamRole(selfUser: selfUser) + pendingSearchTask = Task { + do { + var options = options + options.updateForSelfUserTeamRole(selfUser: userSession.selfUser) - let request = SearchRequest( - query: query.trim(), - searchOptions: options, - team: selfUser.membership?.team - ) + let result = try await searchUsersUseCase.invoke( + query: query, + options: options, + messageProtocol: filterConversation?.messageProtocol + ) - if let task = searchDirectory?.perform(request) { - task.addResultHandler { [weak self] result, isCompleted in - self?.handleSearchResult(result: result, isCompleted: isCompleted) + handleSearchResult(result: result, isCompleted: true) + } catch { + WireLogger.search.warn("Search failed with error: \(error.localizedDescription)") } - task.start() - - pendingSearchTask = task } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/StartUIInviteActionBar.swift b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/StartUIInviteActionBar.swift deleted file mode 100644 index 723e9f32063..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/StartUIInviteActionBar.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit -import WireDesign - -final class StartUIInviteActionBar: UIView { - - var bottomEdgeConstraint: NSLayoutConstraint! - - private(set) var inviteButton: ZMButton! - - private let padding: CGFloat = 12 - - init() { - super.init(frame: .zero) - backgroundColor = SemanticColors.View.backgroundUserCell - - createInviteButton() - createConstraints() - - NotificationCenter.default.addObserver( - self, - selector: #selector(keyboardFrameWillChange(_:)), - name: UIResponder.keyboardWillChangeFrameNotification, - object: nil - ) - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func createInviteButton() { - inviteButton = .init( - style: .accentColorTextButtonStyle, - cornerRadius: 16, - fontSpec: .normalSemiboldFont - ) - inviteButton.titleEdgeInsets = UIEdgeInsets(top: 2, left: 8, bottom: 3, right: 8) - inviteButton.setTitle(L10n.Localizable.Peoplepicker.inviteMorePeople.capitalized, for: .normal) - addSubview(inviteButton) - } - - override var isHidden: Bool { - didSet { - invalidateIntrinsicContentSize() - } - } - - override var intrinsicContentSize: CGSize { - CGSize(width: UIView.noIntrinsicMetric, height: isHidden ? 0 : 56.0) - } - - private func createConstraints() { - inviteButton.translatesAutoresizingMaskIntoConstraints = false - - NSLayoutConstraint.activate([ - inviteButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding * 2), - inviteButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -(padding * 2)), - inviteButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -padding), - inviteButton.topAnchor.constraint(equalTo: topAnchor, constant: padding) - ]) - bottomEdgeConstraint = inviteButton.bottomAnchor.constraint( - equalTo: safeAreaLayoutGuide.bottomAnchor, - constant: -padding - ) - bottomEdgeConstraint.isActive = true - - inviteButton.heightAnchor.constraint(equalToConstant: 56).isActive = true - } - - // MARK: - UIKeyboard notifications - - @objc - private func keyboardFrameWillChange(_ notification: Notification) { - guard let userInfo = notification.userInfo, - let beginOrigin = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.origin.y, - let endOrigin = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.origin.y - else { return } - - let diff: CGFloat = beginOrigin - endOrigin - - UIView.animate(withKeyboardNotification: notification, in: self, animations: { [weak self] _ in - guard let self else { return } - - bottomEdgeConstraint.constant = -padding - (diff > 0 ? 0 : safeAreaInsets.bottom) - layoutIfNeeded() - }) - } -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/StartUIViewController+AddressBook.swift b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/StartUIViewController+AddressBook.swift deleted file mode 100644 index 4a546eead17..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/StartUIViewController+AddressBook.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit -import WireCommonComponents -import WireDataModel -import WireMainNavigationUI - -extension StartUIViewController { - - var needsAddressBookPermission: Bool { - let shouldSkip = AutomationHelper.sharedHelper.skipFirstLoginAlerts || userSession.selfUser.hasTeam - return !AddressBookHelper.sharedHelper.isAddressBookAccessGranted && !shouldSkip - } - - func presentShareContactsViewController() { - let shareContactsViewController = ShareContactsViewController() - shareContactsViewController.delegate = self - navigationController?.pushViewController(shareContactsViewController, animated: true) - } -} - -extension StartUIViewController: ShareContactsViewControllerDelegate { - - func shareContactsViewControllerDidFinish(_ viewController: ShareContactsViewController) { - // called once user has given its contact permission - - if let navigationController = viewController.navigationController { - var viewControllers = navigationController.viewControllers - _ = viewControllers.popLast() - viewControllers.append(ContactsViewController()) - navigationController.setViewControllers(viewControllers, animated: true) - } else { - viewController.dismiss(animated: true) { - self.inviteMoreButtonTapped(nil) - } - } - } - - func shareContactsViewControllerDidSkip(_ viewController: ShareContactsViewController) { - guard - let navigationController, - let sourceView = quickActionsBar.inviteButton?.superview, - let sourceRect = quickActionsBar.inviteButton?.frame.insetBy(dx: -2, dy: -2) - else { return } - - navigationController.popViewController(animated: true) { [weak self] in - self?.presentInviteActivityViewController( - popoverPresentationConfiguration: .sourceView(sourceView, sourceRect), - completionWithItemsHandler: { _, _, _, _ in self?.dismiss(animated: true) } - ) - } - } -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/StartUIViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/StartUIViewController.swift index 6231e66e191..2d5fd8288b4 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/StartUIViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/StartUIViewController.swift @@ -39,8 +39,6 @@ final class StartUIViewController: UIViewController { let searchResultsViewController: SearchResultsViewController - var addressBookHelperType: AddressBookHelperProtocol.Type - let userSession: UserSession let mainCoordinator: AnyMainCoordinator @@ -48,8 +46,6 @@ final class StartUIViewController: UIViewController { let isFederationEnabled: Bool - let quickActionsBar = StartUIInviteActionBar() - let profilePresenter: ProfilePresenter private var emptyResultView: EmptySearchResultsView! @@ -62,7 +58,9 @@ final class StartUIViewController: UIViewController { } var showsGroupSelector: Bool { - SearchGroup.all.count > 1 && userSession.selfUser.canSeeServices + SearchGroup.all.count > 1 && + userSession.selfUser.canSeeServices && + userSession.defaultProtocol != .mls } // MARK: - Init @@ -77,11 +75,7 @@ final class StartUIViewController: UIViewController { return nil } - /// init method for injecting mock addressBookHelper - /// - /// - Parameter addressBookHelperType: a class type conforms AddressBookHelperProtocol init( - addressBookHelperType: AddressBookHelperProtocol.Type = AddressBookHelper.self, isFederationEnabled: Bool = BackendInfo.isFederationEnabled, userSession: UserSession, mainCoordinator: AnyMainCoordinator, @@ -89,7 +83,6 @@ final class StartUIViewController: UIViewController { selfProfileUIBuilder: SelfProfileViewControllerBuilderProtocol ) { self.isFederationEnabled = isFederationEnabled - self.addressBookHelperType = addressBookHelperType self.searchResultsViewController = SearchResultsViewController( userSelection: UserSelection(), userSession: userSession, @@ -166,10 +159,7 @@ final class StartUIViewController: UIViewController { searchResults.searchResultsView.emptyResultView = emptyResultView searchResults.searchResultsView.collectionView.accessibilityIdentifier = "search.list" - quickActionsBar.inviteButton.addTarget(self, action: #selector(inviteMoreButtonTapped(_:)), for: .touchUpInside) - createConstraints() - updateActionBar() searchResults.searchContactList() view.accessibilityViewIsModal = true @@ -254,27 +244,6 @@ final class StartUIViewController: UIViewController { ) } - // MARK: - Action bar - - @objc - func inviteMoreButtonTapped(_ sender: UIButton?) { - if needsAddressBookPermission { - presentShareContactsViewController() - } else { - navigationController?.pushViewController(ContactsViewController(), animated: true) - } - } - - func updateActionBar() { - if !(searchController.searchBar.text?.isEmpty ?? true) || userSession.selfUser.hasTeam { - searchResults.searchResultsView.accessoryView = nil - } else { - searchResults.searchResultsView.accessoryView = quickActionsBar - } - - view.setNeedsLayout() - } - } // MARK: - UISearchResultsUpdating, UISearchBarDelegate @@ -296,4 +265,5 @@ extension StartUIViewController: UISearchResultsUpdating, UISearchBarDelegate { searchBar.text = "" performSearch() } + } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/Devices/OtherUserClientsListViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/Devices/OtherUserClientsListViewController.swift index a3158c47b1a..2f822256aca 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/Devices/OtherUserClientsListViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/Devices/OtherUserClientsListViewController.swift @@ -18,6 +18,7 @@ import SwiftUI import WireDesign +import WireLogging import WireSyncEngine final class OtherUserClientsListViewController: UIViewController, @@ -280,8 +281,12 @@ extension Array where Element: UserClientType { if !certificates.isEmpty { for client in userClients { let mlsClientIdRawValue = mlsClients[client.clientId.hashValue]?.rawValue - client.e2eIdentityCertificate = certificates.first(where: { $0.clientId == mlsClientIdRawValue }) - client.mlsThumbPrint = client.e2eIdentityCertificate?.mlsThumbprint + if let e2eiCertificate = certificates.first(where: { $0.clientId == mlsClientIdRawValue }) { + if userSession.e2eiFeature.isEnabled { + client.e2eIdentityCertificate = e2eiCertificate + } + client.mlsThumbPrint = e2eiCertificate.mlsThumbprint + } updatedUserClients.append(client) } return updatedUserClients diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/Devices/OtherUserDeviceDetailsView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/Devices/OtherUserDeviceDetailsView.swift index 5b4a6df4453..24012c49ad7 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/Devices/OtherUserDeviceDetailsView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/Devices/OtherUserDeviceDetailsView.swift @@ -101,10 +101,10 @@ struct OtherUserDeviceDetailsView: View { var body: some View { ScrollView { VStack(alignment: .leading) { + if let thumbprint = viewModel.mlsThumbprint, !thumbprint.isEmpty { + mlsView + } if viewModel.isE2eIdentityEnabled { - if let thumbprint = viewModel.mlsThumbprint, !thumbprint.isEmpty { - mlsView - } e2eIdentityCertificateView } proteusView diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileActionsFactory.swift b/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileActionsFactory.swift index afa6f4a1190..0503448749c 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileActionsFactory.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileActionsFactory.swift @@ -20,6 +20,7 @@ import Foundation import WireCommonComponents import WireDataModel import WireDesign +import WireLogging import WireSyncEngine /// The actions that can be performed from the profile details or devices. @@ -176,6 +177,14 @@ final class ProfileActionsFactory: ProfileActionsFactoryProtocol { } } + private var canCreateConversationWithOtherDomain: Bool { + if userSession.isFederationUsageAllowed { + true + } else { + viewer.domain == user.domain + } + } + private func makeActionsList(isOneOnOneReady: Bool) -> [ProfileAction] { // Do nothing if the user was deleted @@ -214,7 +223,7 @@ final class ProfileActionsFactory: ProfileActionsFactoryProtocol { switch (context, conversation?.conversationType) { case (_, .oneOnOne?): - if viewer.canCreateConversation(type: .group) { + if viewer.canCreateConversation(type: .group), canCreateConversationWithOtherDomain { actions.append(.createGroup) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileViewController.swift index e530e301565..44cdde89386 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileViewController.swift @@ -19,6 +19,7 @@ import UIKit import WireDataModel import WireDesign +import WireLogging import WireMainNavigationUI import WireSyncEngine @@ -380,7 +381,9 @@ extension ProfileViewController: ProfileFooterViewDelegate, IncomingRequestFoote private func openSelfProfile() { Task { - let selfProfileUI = UINavigationController(rootViewController: selfProfileUIBuilder.build()) + let selfProfileUI = UINavigationController( + rootViewController: selfProfileUIBuilder.build(mainCoordinator: mainCoordinator) + ) selfProfileUI.modalPresentationStyle = .formSheet await mainCoordinator.presentViewController(selfProfileUI) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileViewControllerViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileViewControllerViewModel.swift index 58ca7e20219..98038e27718 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileViewControllerViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/UserProfile/ProfileViewControllerViewModel.swift @@ -18,6 +18,7 @@ import Foundation import WireDataModel +import WireLogging import WireSyncEngine import WireSystem diff --git a/wire-ios/WireCommonComponents/Analytics/Datadog/WireAnalytics+Datadog.swift b/wire-ios/WireCommonComponents/Analytics/Datadog/WireAnalytics+Datadog.swift index 57863ae1c65..a1781dc552e 100644 --- a/wire-ios/WireCommonComponents/Analytics/Datadog/WireAnalytics+Datadog.swift +++ b/wire-ios/WireCommonComponents/Analytics/Datadog/WireAnalytics+Datadog.swift @@ -24,7 +24,7 @@ public extension WireAnalytics { /// Namespace for Datadog analytics. enum Datadog { - private static let shared: WireDatadog = { + static let shared: WireDatadog = { let builder = WireDatadogBuilder() return builder.build() }() @@ -33,42 +33,5 @@ public extension WireAnalytics { public static var userIdentifier: String? { shared.userIdentifier } - - /// Enables Datadog analytics instance if available and makes it a global logger. If Datadog is not available, - /// the function just returns. - /// - Note: this should be called early and **has effect only once** - public static func enable() { - enableOnlyOnce.execute() - } - - static var enableOnlyOnce = OnceOnlyThreadSafeFunction { - shared.enable() - WireLogger.addLogger(shared) - - // pass tags to Datadog through WireLogger - WireLogger.system.addTag(.processId, value: "\(ProcessInfo.processInfo.processIdentifier)") - WireLogger.system.addTag(.processName, value: ProcessInfo.processInfo.processName) - } - } -} - -/// Wrapper class to execute a function just once, thread safe -class OnceOnlyThreadSafeFunction { - private let lock = NSLock() - private var executed = false - private let function: () -> Void - - init(_ function: @escaping () -> Void) { - self.function = function - } - - func execute() { - lock.lock() - defer { lock.unlock() } - - if !executed { - executed = true - function() - } } } diff --git a/wire-ios/WireCommonComponents/Analytics/Datadog/WireDatadog+LoggerProtocol.swift b/wire-ios/WireCommonComponents/Analytics/Datadog/WireDatadog+LoggerProtocol.swift index 7305bab352d..2e093d336e0 100644 --- a/wire-ios/WireCommonComponents/Analytics/Datadog/WireDatadog+LoggerProtocol.swift +++ b/wire-ios/WireCommonComponents/Analytics/Datadog/WireDatadog+LoggerProtocol.swift @@ -18,9 +18,10 @@ import WireAnalytics import WireDatadog +import WireLogging import WireSystem -extension WireDatadog: WireSystem.LoggerProtocol { +extension WireDatadog: LoggerProtocol { public func debug(_ message: any LogConvertible, attributes: LogAttributes...) { log( level: .debug, @@ -80,7 +81,7 @@ extension WireDatadog: WireSystem.LoggerProtocol { // MARK: Helpers private func log( - level: WireDatadog.LogLevel, + level: WireLogLevel, message: any LogConvertible, error: Error? = nil, attributes: [LogAttributes] = [] diff --git a/wire-ios/WireCommonComponents/Analytics/WireAnalytics.swift b/wire-ios/WireCommonComponents/Analytics/WireAnalytics.swift index 1bc97140913..877584dd2f0 100644 --- a/wire-ios/WireCommonComponents/Analytics/WireAnalytics.swift +++ b/wire-ios/WireCommonComponents/Analytics/WireAnalytics.swift @@ -16,5 +16,40 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import Foundation +import WireLogging +import WireSystem + /// Namespace for analytics tools. -public enum WireAnalytics {} +public enum WireAnalytics { + + private static let isSetUpLock = NSLock() + private static var isSetUp = false + + public static func setup() { + // Adding a lock here since some app extension might execute this setup in the same process as the main app. + // https://stackoverflow.com/a/62674277 + isSetUpLock.lock() + defer { isSetUpLock.unlock() } + + guard !isSetUp else { + assertionFailure("WireAnalytics.setup() called more than once") + return + } + isSetUp = true + + WireAnalytics.Datadog.shared.enable() + + WireLogger.initialize( + loggers: [ + SystemLogger(), + CocoaLumberjackLogger(), + WireAnalytics.Datadog.shared + ] + ) + + // pass tags to Datadog through WireLogger + WireLogger.system.addTag(.processId, value: "\(ProcessInfo.processInfo.processIdentifier)") + WireLogger.system.addTag(.processName, value: ProcessInfo.processInfo.processName) + } +} diff --git a/wire-ios/WireCommonComponents/AutomationHelper.swift b/wire-ios/WireCommonComponents/AutomationHelper.swift index 247031631aa..bd93351c2e9 100644 --- a/wire-ios/WireCommonComponents/AutomationHelper.swift +++ b/wire-ios/WireCommonComponents/AutomationHelper.swift @@ -55,14 +55,10 @@ public final class AutomationHelper: NSObject { public let disablePushNotificationAlert: Bool /// Whether autocorrection is disabled public let disableAutocorrection: Bool - /// Whether address book upload is enabled on simulator - let uploadAddressbookOnSimulator: Bool /// Whether we should disable the call quality survey. public let disableCallQualitySurvey: Bool /// Whether we should disable dismissing the conversation input bar keyboard by dragging it downwards. public let disableInteractiveKeyboardDismissal: Bool - /// Delay in address book remote search override - let delayInAddressBookRemoteSearch: TimeInterval? /// Debug data to install in the share container let debugDataToInstall: URL? @@ -77,7 +73,6 @@ public final class AutomationHelper: NSObject { public private(set) var preferredAPIVersion: APIVersion? public private(set) var allowMLSGroupCreation: Bool? - public private(set) var enableMLSSupport: Bool? override init() { let url = URL(string: NSTemporaryDirectory())?.appendingPathComponent(fileArgumentsName) @@ -85,7 +80,6 @@ public final class AutomationHelper: NSObject { self.disablePushNotificationAlert = arguments.hasFlag(AutomationKey.disablePushNotificationAlert) self.disableAutocorrection = arguments.hasFlag(AutomationKey.disableAutocorrection) - self.uploadAddressbookOnSimulator = arguments.hasFlag(AutomationKey.enableAddressBookOnSimulator) self.disableCallQualitySurvey = arguments.hasFlag(AutomationKey.disableCallQualitySurvey) self.shouldPersistBackendType = arguments.hasFlag(AutomationKey.persistBackendType) self.disableInteractiveKeyboardDismissal = arguments.hasFlag(AutomationKey.disableInteractiveKeyboardDismissal) @@ -105,7 +99,6 @@ public final class AutomationHelper: NSObject { } else { self.debugDataToInstall = nil } - self.delayInAddressBookRemoteSearch = AutomationHelper.addressBookSearchDelay(arguments) if let value = arguments.flagValueIfPresent(AutomationKey.preferredAPIVersion.rawValue), let apiVersion = Int32(value) { @@ -113,7 +106,6 @@ public final class AutomationHelper: NSObject { } self.allowMLSGroupCreation = arguments.hasFlag(AutomationKey.allowMLSGroupCreation.rawValue) - self.enableMLSSupport = arguments.hasFlag(AutomationKey.enableMLSSupport.rawValue) super.init() } @@ -126,8 +118,6 @@ public final class AutomationHelper: NSObject { case logTags = "debug-log" case disablePushNotificationAlert = "disable-push-alert" case disableAutocorrection = "disable-autocorrection" - case enableAddressBookOnSimulator = "addressbook-on-simulator" - case addressBookRemoteSearchDelay = "addressbook-search-delay" case debugDataToInstall = "debug-data-to-install" case disableCallQualitySurvey = "disable-call-quality-survey" case persistBackendType = "persist-backend-type" @@ -136,7 +126,6 @@ public final class AutomationHelper: NSObject { case preferredAPIVersion = "preferred-api-version" case allowMLSGroupCreation = "allow-mls-group-creation" case deprecatedCallingUI = "deprecated-calling-ui" - case enableMLSSupport = "enable-mls-support" } /// Returns the login email and password credentials if set in the given arguments @@ -154,15 +143,6 @@ public final class AutomationHelper: NSObject { let tags = tagsString.components(separatedBy: ",") tags.forEach { ZMSLog.set(level: .debug, tag: $0) } } - - /// Returns the custom time interval for address book search delay if it set in the given arguments - private static func addressBookSearchDelay(_ arguments: ArgumentsType) -> TimeInterval? { - guard let delayString = arguments.flagValueIfPresent(AutomationKey.addressBookRemoteSearchDelay.rawValue), - let delay = Int(delayString) else { - return nil - } - return TimeInterval(delay) - } } // MARK: - Helpers diff --git a/wire-ios/WireCommonComponents/FileMetaDataGenerator/FileMetaDataGenerator.swift b/wire-ios/WireCommonComponents/FileMetaDataGenerator/FileMetaDataGenerator.swift index 0c1b266e5b3..2ede0cf3aa9 100644 --- a/wire-ios/WireCommonComponents/FileMetaDataGenerator/FileMetaDataGenerator.swift +++ b/wire-ios/WireCommonComponents/FileMetaDataGenerator/FileMetaDataGenerator.swift @@ -20,6 +20,7 @@ import AVFoundation import Foundation import MobileCoreServices import WireDataModel +import WireLogging private let zmLog = ZMSLog(tag: "UI") diff --git a/wire-ios/WireCommonComponents/LogFileDestination.swift b/wire-ios/WireCommonComponents/LogFileDestination.swift index fbe4d4f0c0e..3328426854a 100644 --- a/wire-ios/WireCommonComponents/LogFileDestination.swift +++ b/wire-ios/WireCommonComponents/LogFileDestination.swift @@ -17,6 +17,7 @@ // import Foundation +import WireLogging import WireSystem public enum LogFileDestination: CaseIterable, FileLoggerDestination { diff --git a/WireFoundation/Sources/WireSystem/Logging/CocoaLumberjackLogger.swift b/wire-ios/WireCommonComponents/Logging/CocoaLumberjackLogger.swift similarity index 70% rename from WireFoundation/Sources/WireSystem/Logging/CocoaLumberjackLogger.swift rename to wire-ios/WireCommonComponents/Logging/CocoaLumberjackLogger.swift index 6acfd97fc2b..45803ab1373 100644 --- a/WireFoundation/Sources/WireSystem/Logging/CocoaLumberjackLogger.swift +++ b/wire-ios/WireCommonComponents/Logging/CocoaLumberjackLogger.swift @@ -18,6 +18,8 @@ import CocoaLumberjackSwift import Foundation +import WireLogging +import WireSystem /// Logger to write logs to fileSystem via CocoaLumberjack final class CocoaLumberjackLogger: LoggerProtocol { @@ -35,31 +37,31 @@ final class CocoaLumberjackLogger: LoggerProtocol { fileLogger.logFileManager.unsortedLogFilePaths.map { URL(fileURLWithPath: $0) } } - func debug(_ message: LogConvertible, attributes: LogAttributes...) { + func debug(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .debug) } - func info(_ message: LogConvertible, attributes: LogAttributes...) { + func info(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .info) } - func notice(_ message: LogConvertible, attributes: LogAttributes...) { + func notice(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .info) } - func warn(_ message: LogConvertible, attributes: LogAttributes...) { + func warn(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .warning) } - func error(_ message: LogConvertible, attributes: LogAttributes...) { + func error(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .error) } - func critical(_ message: LogConvertible, attributes: LogAttributes...) { + func critical(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .error) } - private func log(_ message: LogConvertible, attributes: [LogAttributes], level: DDLogLevel) { + private func log(_ message: any LogConvertible, attributes: [LogAttributes], level: DDLogLevel) { var mergedAttributes: LogAttributes = [:] attributes.forEach { @@ -73,6 +75,22 @@ final class CocoaLumberjackLogger: LoggerProtocol { // return // } + // Filter logs by level: + // Only continue if we're running a DEBUG build or + // the level is greater than or equal to error and lower than or equal to info. + // + // DDLogLevelOff 00000 0 + // DDLogLevelError 00001 1 + // DDLogLevelWarning 00011 3 + // DDLogLevelInfo 00111 7 + // DDLogLevelDebug 01111 15 + // DDLogLevelVerbose 11111 31 + // DDLogLevelAll 1..11111 UInt.max + guard + isDebug || + (level.rawValue >= DDLogLevel.error.rawValue && level.rawValue <= DDLogLevel.info.rawValue) + else { return } + var entry = "[\(formattedLevel(level))] \(message.logDescription)\(attributesDescription(from: mergedAttributes))" @@ -84,7 +102,7 @@ final class CocoaLumberjackLogger: LoggerProtocol { DDLog.log(asynchronous: true, message: formatedMessage) } - public func addTag(_ key: LogAttributesKey, value: String?) { + func addTag(_ key: LogAttributesKey, value: String?) { // do nothing }