diff --git a/.drone.jsonnet b/.drone.jsonnet index d24f88633f..64e175cee2 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -77,10 +77,7 @@ local clean_up_old_test_sims_on_commit_trigger = { { name: 'Build and Run Tests', commands: [ - 'echo "Explicitly running unit tests on \'App_Store_Release\' configuration to ensure optimisation behaviour is consistent"', - 'echo "If tests fail inconsistently from local builds this is likely the difference"', - 'echo ""', - 'NSUnbufferedIO=YES xcodebuild test -project Session.xcodeproj -scheme Session -derivedDataPath ./build/derivedData -resultBundlePath ./build/artifacts/testResults.xcresult -parallelizeTargets -configuration "App_Store_Release" -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES 2>&1 | xcbeautify --is-ci', + './Scripts/build_ci.sh test -resultBundlePath ./build/artifacts/testResults.xcresult -destination "platform=iOS Simulator,id=$(<./build/artifacts/sim_uuid)" -parallel-testing-enabled NO -test-timeouts-enabled YES -maximum-test-execution-time-allowance 10 -collect-test-diagnostics never ENABLE_TESTABILITY=YES', ], depends_on: [ 'Reset SPM Cache if Needed', @@ -99,31 +96,27 @@ local clean_up_old_test_sims_on_commit_trigger = { status: ['success', 'failure'], }, }, - { - name: 'Check for Build/Test Failures', - commands: [ - 'echo "Checking for build errors or test failures in xcresult bundle..."', - 'xcresultparser --output-format cli --failed-tests-only ./build/artifacts/testResults.xcresult' - ], - depends_on: ['Build and Run Tests'] - }, { name: 'Log Failed Test Summary', commands: [ 'echo "--- FAILED TESTS ---"', - 'xcresultparser --output-format cli --failed-tests-only ./build/artifacts/testResults.xcresult' + 'xcresultparser --output-format cli --failed-tests-only ./build/artifacts/testResults.xcresult', + 'exit 1' // Always fail if this runs to make it more obvious in the UI ], - depends_on: ['Check for Build/Test Failures'], + depends_on: ['Build and Run Tests'], when: { status: ['failure'], // Only run this on failure }, }, { - name: 'Convert xcresult to xml', + name: 'Generate Code Coverage Report', commands: [ 'xcresultparser --output-format cobertura ./build/artifacts/testResults.xcresult > ./build/artifacts/coverage.xml', ], depends_on: ['Build and Run Tests'], + when: { + status: ['success'], + }, }, ], }, @@ -157,8 +150,7 @@ local clean_up_old_test_sims_on_commit_trigger = { { name: 'Build', commands: [ - 'mkdir build', - 'NSUnbufferedIO=YES && xcodebuild archive -project Session.xcodeproj -scheme Session -derivedDataPath ./build/derivedData -parallelizeTargets -configuration "App_Store_Release" -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | xcbeautify --is-ci', + './Scripts/build_ci.sh archive -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator"', ], depends_on: [ 'Reset SPM Cache if Needed', diff --git a/Scripts/EmojiGenerator.swift b/Scripts/EmojiGenerator.swift index 519ce65220..1c2102c81f 100755 --- a/Scripts/EmojiGenerator.swift +++ b/Scripts/EmojiGenerator.swift @@ -53,7 +53,7 @@ enum RemoteModel { case flags = "Flags" case components = "Component" - var localizedKey: String = { + var localizedKey: String { switch self { case .smileys: return "Smileys" @@ -77,8 +77,8 @@ enum RemoteModel { return "Flags" case .components: return "Component" - } - }() + } + } } static func fetchEmojiData() throws -> Data { @@ -569,8 +569,9 @@ extension EmojiGenerator { fileHandle.indent { let stringKey = "emojiCategory\(category.localizedKey)" let stringComment = "The name for the emoji category '\(category.rawValue)'" - - fileHandle.writeLine("return NSLocalizedString(\"\(stringKey)\", comment: \"\(stringComment)\")") + + fileHandle.writeLine("// \(stringComment)") + fileHandle.writeLine("return \"\(stringKey)\".localized()") } } fileHandle.writeLine("}") diff --git a/Scripts/build_ci.sh b/Scripts/build_ci.sh new file mode 100755 index 0000000000..9ff7932d3d --- /dev/null +++ b/Scripts/build_ci.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +IFS=$' \t\n' + +set -euo pipefail + +if [ $# -lt 1 ]; then + echo "Error: Missing mode. Usage: $0 [test|archive] [unique_xcodebuild_args...]" + exit 1 +fi + +MODE="$1" +shift + +COMMON_ARGS=( + -project Session.xcodeproj + -scheme Session + -derivedDataPath ./build/derivedData + -parallelizeTargets + -configuration "App_Store_Release" +) + +UNIQUE_ARGS=("$@") +XCODEBUILD_RAW_LOG=$(mktemp) + +trap 'rm -f "$XCODEBUILD_RAW_LOG"' EXIT + +if [[ "$MODE" == "test" ]]; then + + echo "--- Running Build and Unit Tests (App_Store_Release) ---" + + xcodebuild_exit_code=0 + + # We wrap the pipeline in parentheses to capture the exit code of xcodebuild + # which is at PIPESTATUS[0]. We do not use tee to a file here, as the complexity + # of reading back the UUID is not necessary if we pass it via args. + ( + NSUnbufferedIO=YES xcodebuild test \ + "${COMMON_ARGS[@]}" \ + "${UNIQUE_ARGS[@]}" 2>&1 | tee "$XCODEBUILD_RAW_LOG" | xcbeautify --is-ci + ) || xcodebuild_exit_code=${PIPESTATUS[0]} + + echo "" + echo "--- xcodebuild finished with exit code: $xcodebuild_exit_code ---" + + if [ "$xcodebuild_exit_code" -eq 0 ]; then + echo "✅ All tests passed and build succeeded!" + exit 0 + fi + + echo "" + echo "🔴 Build failed" + echo "----------------------------------------------------" + echo "Checking for test failures in xcresult bundle..." + + xcresultparser --output-format cli --no-test-result --coverage ./build/artifacts/testResults.xcresult + parser_output=$(xcresultparser --output-format cli --no-test-result ./build/artifacts/testResults.xcresult) + + build_errors_count=$(echo "$parser_output" | grep "Number of errors" | awk '{print $NF}') + failed_tests_count=$(echo "$parser_output" | grep "Number of failed tests" | awk '{print $NF}') + + if [ "${build_errors_count:-0}" -gt 0 ] || [ "${failed_tests_count:-0}" -gt 0 ]; then + echo "" + echo "🔴 Found $build_errors_count build error(s) and $failed_tests_count failed test(s) in the xcresult bundle." + exit 1 + else + echo "No test failures found in results. Failure was likely a build error." + echo "" + + echo "--- Summary of Potential Build Errors ---" + grep -E --color=always '(:[0-9]+:[0-9]+: error:)|(ld: error:)|(error: linker command failed)|(PhaseScriptExecution)|(rsync error:)' "$XCODEBUILD_RAW_LOG" || true + echo "" + echo "--- End of Raw Log ---" + tail -n 20 "$XCODEBUILD_RAW_LOG" + echo "-------------------------" + exit "$xcodebuild_exit_code" + fi + + echo "----------------------------------------------------" + exit "$xcodebuild_exit_code" + +elif [[ "$MODE" == "archive" ]]; then + + echo "--- Running Simulator Archive Build (App_Store_Release) ---" + + NSUnbufferedIO=YES xcodebuild archive \ + "${COMMON_ARGS[@]}" \ + "${UNIQUE_ARGS[@]}" 2>&1 | xcbeautify --is-ci + +else + echo "Error: Invalid mode '$MODE' specified. Use 'test' or 'archive'." + exit 1 +fi diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index c3c86fd017..2ab6a4f486 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -98,7 +98,7 @@ 7B4EF25A2934743000CB351D /* SessionTableViewTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4EF2592934743000CB351D /* SessionTableViewTitleView.swift */; }; 7B50D64D28AC7CF80086CCEC /* silence.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 7B50D64C28AC7CF80086CCEC /* silence.aiff */; }; 7B5233C42900E90F00F8F375 /* SessionLabelCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5233C32900E90F00F8F375 /* SessionLabelCarouselView.swift */; }; - 7B5233C6290636D700F8F375 /* _018_DisappearingMessagesConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5233C5290636D700F8F375 /* _018_DisappearingMessagesConfiguration.swift */; }; + 7B5233C6290636D700F8F375 /* _032_DisappearingMessagesConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5233C5290636D700F8F375 /* _032_DisappearingMessagesConfiguration.swift */; }; 7B5802992AAEF1B50050EEB1 /* OpenGroupInvitationView_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5802982AAEF1B50050EEB1 /* OpenGroupInvitationView_SwiftUI.swift */; }; 7B7037432834B81F000DCF35 /* ReactionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037422834B81F000DCF35 /* ReactionContainerView.swift */; }; 7B7037452834BCC0000DCF35 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037442834BCC0000DCF35 /* ReactionView.swift */; }; @@ -106,9 +106,7 @@ 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */; }; 7B7CB190270FB2150079FF93 /* MiniCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */; }; 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */; }; - 7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682228A4C1210069F315 /* UpdateTypes.swift */; }; - 7B81682828B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */; }; - 7B81682A28B6F1420069F315 /* ReactionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682928B6F1420069F315 /* ReactionResponse.swift */; }; + 7B81682828B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682728B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift */; }; 7B81682C28B72F480069F315 /* PendingChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682B28B72F480069F315 /* PendingChange.swift */; }; 7B81FB5A2AB01B17002FB267 /* LoadingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81FB582AB01AA8002FB267 /* LoadingIndicatorView.swift */; }; 7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */; }; @@ -129,7 +127,7 @@ 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA68908272A27BE00EFC32F /* SessionCall.swift */; }; 7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */; }; 7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */; }; - 7BAA7B6628D2DE4700AE1489 /* _009_OpenGroupPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */; }; + 7BAA7B6628D2DE4700AE1489 /* _018_OpenGroupPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAA7B6528D2DE4700AE1489 /* _018_OpenGroupPermission.swift */; }; 7BAADFCC27B0EF23007BCF92 /* CallVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAADFCB27B0EF23007BCF92 /* CallVideoView.swift */; }; 7BAADFCE27B215FE007BCF92 /* UIView+Draggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAADFCD27B215FE007BCF92 /* UIView+Draggable.swift */; }; 7BAF54CF27ACCEEC003D12F8 /* GlobalSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54CC27ACCEEC003D12F8 /* GlobalSearchViewController.swift */; }; @@ -174,16 +172,13 @@ 9422EE2B2B8C3A97004C740D /* String+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9422EE2A2B8C3A97004C740D /* String+Utilities.swift */; }; 942ADDD42D9F9613006E0BB0 /* NewTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942ADDD32D9F960C006E0BB0 /* NewTagView.swift */; }; 943C6D822B75E061004ACE64 /* Message+DisappearingMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943C6D812B75E061004ACE64 /* Message+DisappearingMessages.swift */; }; - 945D9C582D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945D9C572D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift */; }; 946F5A732D5DA3AC00A5ADCE /* Punycode in Frameworks */ = {isa = PBXBuildFile; productRef = 946F5A722D5DA3AC00A5ADCE /* Punycode */; }; 9473386E2BDF5F3E00B9E169 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9473386D2BDF5F3E00B9E169 /* InfoPlist.xcstrings */; }; 9479981C2DD44ADC008F5CD5 /* ThreadNotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9479981B2DD44AC5008F5CD5 /* ThreadNotificationSettingsViewModel.swift */; }; - 947D7FD42D509FC900E8E413 /* SessionNetworkAPI+Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD12D509FC900E8E413 /* SessionNetworkAPI+Models.swift */; }; 947D7FD62D509FC900E8E413 /* SessionNetworkAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FCF2D509FC900E8E413 /* SessionNetworkAPI.swift */; }; - 947D7FD72D509FC900E8E413 /* SessionNetworkAPI+Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD22D509FC900E8E413 /* SessionNetworkAPI+Network.swift */; }; - 947D7FD82D509FC900E8E413 /* SessionNetworkAPI+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD02D509FC900E8E413 /* SessionNetworkAPI+Database.swift */; }; + 947D7FD72D509FC900E8E413 /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD22D509FC900E8E413 /* HTTPClient.swift */; }; + 947D7FD82D509FC900E8E413 /* KeyValueStore+SessionNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD02D509FC900E8E413 /* KeyValueStore+SessionNetwork.swift */; }; 947D7FDE2D5180F200E8E413 /* SessionNetworkScreen+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FDB2D5180F200E8E413 /* SessionNetworkScreen+ViewModel.swift */; }; - 947D7FE32D5181F400E8E413 /* _006_RenameTableSettingToKeyValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE22D5181F400E8E413 /* _006_RenameTableSettingToKeyValueStore.swift */; }; 947D7FE72D51837200E8E413 /* ArrowCapsule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE42D51837200E8E413 /* ArrowCapsule.swift */; }; 947D7FE82D51837200E8E413 /* PopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE52D51837200E8E413 /* PopoverView.swift */; }; 947D7FE92D51837200E8E413 /* Text+CopyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE62D51837200E8E413 /* Text+CopyButton.swift */; }; @@ -202,7 +197,7 @@ 94C58AC92D2E037200609195 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C58AC82D2E036E00609195 /* Permissions.swift */; }; 94CD95BB2E08D9E00097754D /* SessionProBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD95BA2E08D9D40097754D /* SessionProBadge.swift */; }; 94CD95BD2E09083C0097754D /* LibSession+Pro.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD95BC2E0908340097754D /* LibSession+Pro.swift */; }; - 94CD95C12E0CBF430097754D /* _029_AddProMessageFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD95C02E0CBF1C0097754D /* _029_AddProMessageFlag.swift */; }; + 94CD95C12E0CBF430097754D /* _044_AddProMessageFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD95C02E0CBF1C0097754D /* _044_AddProMessageFlag.swift */; }; 94CD962D2E1B85920097754D /* InputViewButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD962B2E1B85920097754D /* InputViewButton.swift */; }; 94CD962E2E1B85920097754D /* InputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD962A2E1B85920097754D /* InputTextView.swift */; }; 94CD96302E1B88430097754D /* CGRect+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94CD962F2E1B88430097754D /* CGRect+Utilities.swift */; }; @@ -251,7 +246,6 @@ B8856D72256F1421001CE70E /* OWSWindowManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2FB255B6DBD007E1867 /* OWSWindowManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; B8856DE6256F15F2001CE70E /* String+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB3F255A580C00E217F9 /* String+SSK.swift */; }; B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF237255B6D65007E1867 /* UIDevice+featureSupport.swift */; }; - B886B4A72398B23E00211ABE /* (null) in Sources */ = {isa = PBXBuildFile; }; B886B4A92398BA1500211ABE /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A82398BA1500211ABE /* QRCode.swift */; }; B88FA7F2260C3EB10049422F /* OpenGroupSuggestionGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88FA7F1260C3EB10049422F /* OpenGroupSuggestionGrid.swift */; }; B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */; }; @@ -268,7 +262,7 @@ B8D0A25925E367AC00C1835E /* Notification+MessageReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D0A25825E367AC00C1835E /* Notification+MessageReceiver.swift */; }; B8D0A26925E4A2C200C1835E /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D0A26825E4A2C200C1835E /* Onboarding.swift */; }; B8D64FBB25BA78310029CFC0 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; }; - B8D64FBD25BA78310029CFC0 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; + B8D64FBD25BA78310029CFC0 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; }; B8D64FBE25BA78310029CFC0 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; B8D64FC725BA78520029CFC0 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; }; B8D64FCB25BA78A90029CFC0 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; @@ -286,7 +280,7 @@ C300A5F22554B09800555489 /* MessageSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5F12554B09800555489 /* MessageSender.swift */; }; C300A60D2554B31900555489 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5CE2553860700C340D1 /* Logging.swift */; }; C302093E25DCBF08001F572D /* MentionSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C302093D25DCBF07001F572D /* MentionSelectionView.swift */; }; - C32824D325C9F9790062D0A7 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; + C32824D325C9F9790062D0A7 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; }; C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328250E25CA06020062D0A7 /* VoiceMessageView.swift */; }; C328251F25CA3A900062D0A7 /* QuoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328251E25CA3A900062D0A7 /* QuoteView.swift */; }; C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328252F25CA55360062D0A7 /* ContextMenuWindow.swift */; }; @@ -314,13 +308,12 @@ C331FFFE2558FF3B00070591 /* FullConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82AA238F669C00BA5194 /* FullConversationCell.swift */; }; C33FD9B3255A548A00E217F9 /* SignalUtilitiesKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C33FD9C2255A54EF00E217F9 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; }; - C33FD9C4255A54EF00E217F9 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; + C33FD9C4255A54EF00E217F9 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; }; C33FD9C5255A54EF00E217F9 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; C33FDC58255A582000E217F9 /* ReverseDispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */; }; C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */; }; C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; }; C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */; }; - C352A2FF25574B6300338F3E /* (null) in Sources */ = {isa = PBXBuildFile; }; C3548F0824456AB6009433A8 /* UIView+Wrapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */; }; C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C354E75923FE2A7600CE22E3 /* BaseVC.swift */; }; C35D0DB525AE5F1200B6BF49 /* UIEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35D0DB425AE5F1200B6BF49 /* UIEdgeInsets.swift */; }; @@ -329,7 +322,7 @@ C374EEF425DB31D40073A857 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C374EEF325DB31D40073A857 /* VoiceMessageRecordingView.swift */; }; C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C379DCF3256735770002D4EB /* VisibleMessage+Attachment.swift */; }; C37F5414255BAFA7002AEA92 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; - C37F54DC255BB84A002AEA92 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; + C37F54DC255BB84A002AEA92 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; }; C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; C38EF24D255B6D67007E1867 /* UIView+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF240255B6D67007E1867 /* UIView+OWS.swift */; }; C38EF24E255B6D67007E1867 /* Collection+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF241255B6D67007E1867 /* Collection+OWS.swift */; }; @@ -373,16 +366,14 @@ C3ADC66126426688005F1414 /* ShareNavController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ADC66026426688005F1414 /* ShareNavController.swift */; }; C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */; }; C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */; }; - C3C2A5A3255385C100C340D1 /* SessionSnodeKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A5A1255385C100C340D1 /* SessionSnodeKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C3C2A5A7255385C100C340D1 /* SessionSnodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B9255385ED00C340D1 /* Configuration.swift */; }; - C3C2A5E02553860B00C340D1 /* Threading+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D42553860A00C340D1 /* Threading+SSK.swift */; }; + C3C2A5A3255385C100C340D1 /* SessionNetworkingKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A5A1255385C100C340D1 /* SessionNetworkingKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3C2A5A7255385C100C340D1 /* SessionNetworkingKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */; }; C3C2A681255388CC00C340D1 /* SessionUtilitiesKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C3C2A6C62553896A00C340D1 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; C3C2A6F425539DE700C340D1 /* SessionMessagingKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3C2A6F225539DE700C340D1 /* SessionMessagingKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; C3C2A6F825539DE700C340D1 /* SessionMessagingKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; + C3C2A70B25539E1E00C340D1 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; }; C3C2A74425539EB700C340D1 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A74325539EB700C340D1 /* Message.swift */; }; C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A74C2553A39700C340D1 /* VisibleMessage.swift */; }; C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A75E2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift */; }; @@ -423,15 +414,13 @@ FD01504B2CA243CB005B08A1 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0150472CA243CB005B08A1 /* Mock.swift */; }; FD01504C2CA243CB005B08A1 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0150472CA243CB005B08A1 /* Mock.swift */; }; FD01504E2CA243E7005B08A1 /* TypeConversionUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD01504D2CA243E7005B08A1 /* TypeConversionUtilitiesSpec.swift */; }; - FD0150502CA24468005B08A1 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; platformFilter = ios; }; + FD0150502CA24468005B08A1 /* SessionNetworkingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; platformFilter = ios; }; FD0150522CA2446D005B08A1 /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = FD0150512CA2446D005B08A1 /* Quick */; }; FD0150542CA24471005B08A1 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD0150532CA24471005B08A1 /* Nimble */; }; FD0150582CA27DF3005B08A1 /* ScrollableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0150572CA27DEE005B08A1 /* ScrollableLabel.swift */; }; - FD02CC122C367762009AB976 /* Request+PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC112C367761009AB976 /* Request+PushNotificationAPI.swift */; }; - FD02CC142C3677E6009AB976 /* Request+OpenGroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC132C3677E6009AB976 /* Request+OpenGroupAPI.swift */; }; FD02CC162C3681EF009AB976 /* RevokeSubaccountResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC152C3681EF009AB976 /* RevokeSubaccountResponse.swift */; }; FD05593D2DFA3A2800DC48CE /* VoipPayloadKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05593C2DFA3A2200DC48CE /* VoipPayloadKey.swift */; }; - FD05594E2E012D2700DC48CE /* _028_RenameAttachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05594D2E012D1A00DC48CE /* _028_RenameAttachments.swift */; }; + FD05594E2E012D2700DC48CE /* _043_RenameAttachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD05594D2E012D1A00DC48CE /* _043_RenameAttachments.swift */; }; FD0559562E026E1B00DC48CE /* ObservingDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0559542E026CC900DC48CE /* ObservingDatabase.swift */; }; FD0606C32BCE13ED00C3816E /* MessageRequestFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0606C22BCE13ED00C3816E /* MessageRequestFooterView.swift */; }; FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */; }; @@ -453,7 +442,7 @@ FD09799927FFC1A300936362 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09799827FFC1A300936362 /* Attachment.swift */; }; FD09799B27FFC82D00936362 /* Quote.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09799A27FFC82D00936362 /* Quote.swift */; }; FD09B7E328865FDA00ED0B66 /* HighlightMentionBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E228865FDA00ED0B66 /* HighlightMentionBackgroundView.swift */; }; - FD09B7E5288670BB00ED0B66 /* _008_EmojiReacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E4288670BB00ED0B66 /* _008_EmojiReacts.swift */; }; + FD09B7E5288670BB00ED0B66 /* _017_EmojiReacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E4288670BB00ED0B66 /* _017_EmojiReacts.swift */; }; FD09B7E7288670FD00ED0B66 /* Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09B7E6288670FD00ED0B66 /* Reaction.swift */; }; FD09C5E628260FF9000CE219 /* MediaGalleryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E528260FF9000CE219 /* MediaGalleryViewModel.swift */; }; FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E728264937000CE219 /* MediaDetailViewController.swift */; }; @@ -466,7 +455,6 @@ FD10AF122AF85D11007709E5 /* Feature+ServiceNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD10AF112AF85D11007709E5 /* Feature+ServiceNetwork.swift */; }; FD11E22D2CA4D12C001BAF58 /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = FD2286782C38D4FF00BC06F7 /* DifferenceKit */; }; FD11E22E2CA4D12C001BAF58 /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = FDEF57292C3CF50B00131302 /* WebRTC */; }; - FD12A83D2AD63BCC00EEBA0D /* EditableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD12A83C2AD63BCC00EEBA0D /* EditableState.swift */; }; FD12A83F2AD63BDF00EEBA0D /* Navigatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD12A83E2AD63BDF00EEBA0D /* Navigatable.swift */; }; FD12A8412AD63BEA00EEBA0D /* NavigatableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD12A8402AD63BEA00EEBA0D /* NavigatableState.swift */; }; FD12A8432AD63BF600EEBA0D /* ObservableTableSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD12A8422AD63BF600EEBA0D /* ObservableTableSource.swift */; }; @@ -476,18 +464,12 @@ FD16AB5B2A1DD7CA0083D849 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */; }; FD16AB5F2A1DD98F0083D849 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; }; FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */; }; - FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */; }; - FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */; }; + FD17D79927F40AB800122BE0 /* _009_SMK_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _009_SMK_YDBToGRDBMigration.swift */; }; FD17D7A127F40D2500122BE0 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F527EAD44C00FF65E7 /* Storage.swift */; }; - FD17D7A227F40F0500122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */; }; - FD17D7A427F40F8100122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7A327F40F8100122BE0 /* _003_YDBToGRDBMigration.swift */; }; + FD17D7A227F40F0500122BE0 /* _006_SMK_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79527F3E04600122BE0 /* _006_SMK_InitialSetupMigration.swift */; }; FD17D7AE27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */; }; FD17D7B827F51ECA00122BE0 /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B727F51ECA00122BE0 /* Migration.swift */; }; - FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */; }; - FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */; }; - FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */; }; FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E427F6A09900122BE0 /* Identity.swift */; }; - FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */; }; FD19363C2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD19363B2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift */; }; FD19363F2ACA66DE004BCF0F /* DatabaseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD19363E2ACA66DE004BCF0F /* DatabaseSpec.swift */; }; FD1A553E2E14BE11003761E4 /* PagedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A553D2E14BE0E003761E4 /* PagedData.swift */; }; @@ -495,7 +477,7 @@ FD1A55432E179AED003761E4 /* ObservableKeyEvent+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A55422E179AE6003761E4 /* ObservableKeyEvent+Utilities.swift */; }; FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */; }; FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */; }; - FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */; }; + FD1D732E2A86114600E3F410 /* _029_BlockCommunityMessageRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D732D2A86114600E3F410 /* _029_BlockCommunityMessageRequests.swift */; }; FD22726B2C32911C004D8A6C /* SendReadReceiptsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272532C32911A004D8A6C /* SendReadReceiptsJob.swift */; }; FD22726C2C32911C004D8A6C /* GroupLeavingJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272542C32911A004D8A6C /* GroupLeavingJob.swift */; }; FD22726D2C32911C004D8A6C /* CheckForAppUpdatesJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272552C32911A004D8A6C /* CheckForAppUpdatesJob.swift */; }; @@ -545,9 +527,7 @@ FD2272D42C34ECE1004D8A6C /* BencodeEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272D32C34ECE1004D8A6C /* BencodeEncoder.swift */; }; FD2272D62C34ED6A004D8A6C /* RetryWithDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */; }; FD2272D82C34EDE7004D8A6C /* SnodeAPIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272D72C34EDE6004D8A6C /* SnodeAPIEndpoint.swift */; }; - FD2272D92C34EED6004D8A6C /* _001_ThemePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9F828A5F14A003AE748 /* _001_ThemePreferences.swift */; }; FD2272DD2C34EFFA004D8A6C /* AppSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272DC2C34EFFA004D8A6C /* AppSetup.swift */; }; - FD2272DE2C34F11F004D8A6C /* _001_ThemePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9F828A5F14A003AE748 /* _001_ThemePreferences.swift */; }; FD2272E02C3502BE004D8A6C /* Setting+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272DF2C3502BE004D8A6C /* Setting+Theme.swift */; }; FD2272E62C351378004D8A6C /* SUIKImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272E52C351378004D8A6C /* SUIKImageFormat.swift */; }; FD2272EA2C351CA7004D8A6C /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2272E92C351CA7004D8A6C /* Threading.swift */; }; @@ -592,7 +572,6 @@ FD245C5D2850660F00B966DD /* OWSAudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F7255B6DBC007E1867 /* OWSAudioPlayer.m */; }; FD245C5F2850662200B966DD /* OWSWindowManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF306255B6DBE007E1867 /* OWSWindowManager.m */; }; FD245C632850664600B966DD /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD245C612850664300B966DD /* Configuration.swift */; }; - FD245C662850665900B966DD /* OpenGroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88FA7B726045D100049422F /* OpenGroupAPI.swift */; }; FD245C682850666300B966DD /* Message+Destination.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A30825574D8400338F3E /* Message+Destination.swift */; }; FD245C692850666800B966DD /* ExpirationTimerUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5E62554B07300555489 /* ExpirationTimerUpdate.swift */; }; FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; }; @@ -606,7 +585,7 @@ FD2DD5902C6DD13C0073D9BE /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = FD2DD58F2C6DD13C0073D9BE /* DifferenceKit */; }; FD336F602CAA28CF00C0B51B /* CommonSMKMockExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F562CAA28CF00C0B51B /* CommonSMKMockExtensions.swift */; }; FD336F612CAA28CF00C0B51B /* MockNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F5C2CAA28CF00C0B51B /* MockNotificationsManager.swift */; }; - FD336F622CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift */; }; + FD336F622CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift */; }; FD336F632CAA28CF00C0B51B /* MockOGMCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F5D2CAA28CF00C0B51B /* MockOGMCache.swift */; }; FD336F642CAA28CF00C0B51B /* MockCommunityPollerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F582CAA28CF00C0B51B /* MockCommunityPollerCache.swift */; }; FD336F652CAA28CF00C0B51B /* MockDisplayPictureCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F592CAA28CF00C0B51B /* MockDisplayPictureCache.swift */; }; @@ -623,8 +602,8 @@ FD336F742CABB97800C0B51B /* PreparedRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0150302CA24310005B08A1 /* PreparedRequestSpec.swift */; }; FD336F752CABB97800C0B51B /* RequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0150312CA24310005B08A1 /* RequestSpec.swift */; }; FD336F762CABB97800C0B51B /* BencodeResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD383722AFDD6D7001367F2 /* BencodeResponseSpec.swift */; }; - FD3559462CC1FF200088F2A9 /* _020_AddMissingWhisperFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3559452CC1FF140088F2A9 /* _020_AddMissingWhisperFlag.swift */; }; - FD368A6829DE8F9C000DBF1E /* _012_AddFTSIfNeeded.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */; }; + FD3559462CC1FF200088F2A9 /* _034_AddMissingWhisperFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3559452CC1FF140088F2A9 /* _034_AddMissingWhisperFlag.swift */; }; + FD368A6829DE8F9C000DBF1E /* _026_AddFTSIfNeeded.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6729DE8F9B000DBF1E /* _026_AddFTSIfNeeded.swift */; }; FD368A6A29DE9E30000DBF1E /* UIContextualAction+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */; }; FD3765DF2AD8F03100DC1489 /* MockSnodeAPICache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765DE2AD8F03100DC1489 /* MockSnodeAPICache.swift */; }; FD3765E02AD8F05100DC1489 /* MockSnodeAPICache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765DE2AD8F03100DC1489 /* MockSnodeAPICache.swift */; }; @@ -633,8 +612,6 @@ FD3765E72ADE1AA300DC1489 /* UnrevokeSubaccountRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765E62ADE1AA300DC1489 /* UnrevokeSubaccountRequest.swift */; }; FD3765E92ADE1AAE00DC1489 /* UnrevokeSubaccountResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765E82ADE1AAE00DC1489 /* UnrevokeSubaccountResponse.swift */; }; FD3765EA2ADE37B400DC1489 /* Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD47E0AA2AA68EEA00A55E41 /* Authentication.swift */; }; - FD3765F42ADE5A0800DC1489 /* AuthenticatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765F32ADE5A0800DC1489 /* AuthenticatedRequest.swift */; }; - FD3765F62ADE5BA500DC1489 /* ServiceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765F52ADE5BA500DC1489 /* ServiceInfo.swift */; }; FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */; }; FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */; }; FD37E9C828A1D73F003AE748 /* Theme+Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */; }; @@ -652,13 +629,12 @@ FD37EA0128A60473003AE748 /* UIKit+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0028A60473003AE748 /* UIKit+Theme.swift */; }; FD37EA0328A9FDCC003AE748 /* HelpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0228A9FDCC003AE748 /* HelpViewModel.swift */; }; FD37EA0528AA00C1003AE748 /* NotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0428AA00C1003AE748 /* NotificationSettingsViewModel.swift */; }; - FD37EA0D28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0C28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift */; }; - FD37EA0F28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */; }; + FD37EA0D28AB2A45003AE748 /* _013_FixDeletedMessageReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0C28AB2A45003AE748 /* _013_FixDeletedMessageReadState.swift */; }; + FD37EA0F28AB3330003AE748 /* _014_FixHiddenModAdminSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0E28AB3330003AE748 /* _014_FixHiddenModAdminSupport.swift */; }; FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA1428AB42CB003AE748 /* IdentitySpec.swift */; }; FD37EA1728AC5605003AE748 /* NotificationContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */; }; FD37EA1928AC5CCA003AE748 /* NotificationSoundViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA1828AC5CCA003AE748 /* NotificationSoundViewModel.swift */; }; FD39352C28F382920084DADA /* VersionFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD39352B28F382920084DADA /* VersionFooterView.swift */; }; - FD39353628F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD39353528F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift */; }; FD3937082E4AD3FE00571F17 /* NoopDependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3937072E4AD3F800571F17 /* NoopDependency.swift */; }; FD39370C2E4D7BCA00571F17 /* DocumentPickerHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD39370B2E4D7BBE00571F17 /* DocumentPickerHandler.swift */; }; FD3AABE928306BBD00E5099A /* ThreadPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */; }; @@ -677,13 +653,12 @@ FD428B192B4B576F006D0888 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD428B182B4B576F006D0888 /* AppContext.swift */; }; FD428B1B2B4B6098006D0888 /* Notifications+Lifecycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD428B1A2B4B6098006D0888 /* Notifications+Lifecycle.swift */; }; FD428B1F2B4B758B006D0888 /* AppReadiness.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD428B1E2B4B758B006D0888 /* AppReadiness.swift */; }; - FD428B232B4B9969006D0888 /* _017_RebuildFTSIfNeeded_2_4_5.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD428B222B4B9969006D0888 /* _017_RebuildFTSIfNeeded_2_4_5.swift */; }; + FD428B232B4B9969006D0888 /* _031_RebuildFTSIfNeeded_2_4_5.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD428B222B4B9969006D0888 /* _031_RebuildFTSIfNeeded_2_4_5.swift */; }; FD42ECCE2E287CD4002D03EA /* ThemeColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD42ECCD2E287CD1002D03EA /* ThemeColor.swift */; }; FD42ECD02E289261002D03EA /* ThemeLinearGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD42ECCF2E289257002D03EA /* ThemeLinearGradient.swift */; }; FD42ECD22E3071DE002D03EA /* ThemeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD42ECD12E3071DC002D03EA /* ThemeText.swift */; }; FD42ECD42E32FF2E002D03EA /* StringUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD42ECD32E32FF2A002D03EA /* StringUtilitiesSpec.swift */; }; FD42ECD62E3308B5002D03EA /* ObservableKey+SessionUtilitiesKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD42ECD52E3308AC002D03EA /* ObservableKey+SessionUtilitiesKit.swift */; }; - FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; }; FD432434299C6985008A0213 /* PendingReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD432433299C6985008A0213 /* PendingReadReceipt.swift */; }; FD47E0B12AA6A05800A55E41 /* Authentication+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD47E0B02AA6A05800A55E41 /* Authentication+SessionMessagingKit.swift */; }; FD47E0B52AA6D7AA00A55E41 /* Request+SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD47E0B42AA6D7AA00A55E41 /* Request+SnodeAPI.swift */; }; @@ -695,7 +670,7 @@ FD481A972CAE0AE000ECC4CF /* MockAppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD481A932CAE0ADD00ECC4CF /* MockAppContext.swift */; }; FD481A992CB4CAAA00ECC4CF /* MockLibSessionCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F5B2CAA28CF00C0B51B /* MockLibSessionCache.swift */; }; FD481A9A2CB4CAE500ECC4CF /* CommonSMKMockExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F562CAA28CF00C0B51B /* CommonSMKMockExtensions.swift */; }; - FD481A9B2CB4CAF100ECC4CF /* CustomArgSummaryDescribable+SMK.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift */; }; + FD481A9B2CB4CAF100ECC4CF /* CustomArgSummaryDescribable+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift */; }; FD481A9C2CB4D58300ECC4CF /* MockSnodeAPICache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765DE2AD8F03100DC1489 /* MockSnodeAPICache.swift */; }; FD481AA32CB889AE00ECC4CF /* RetrieveDefaultOpenGroupRoomsJobSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD481AA22CB889A400ECC4CF /* RetrieveDefaultOpenGroupRoomsJobSpec.swift */; }; FD49E2462B05C1D500FFBBB5 /* MockKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD49E2452B05C1D500FFBBB5 /* MockKeychain.swift */; }; @@ -704,9 +679,8 @@ FD49E2492B05C1D500FFBBB5 /* MockKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD49E2452B05C1D500FFBBB5 /* MockKeychain.swift */; }; FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4B200D283492210034334B /* InsetLockableTableView.swift */; }; FD4BB22B2D63F20700D0DC3D /* MigrationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4BB22A2D63F20600D0DC3D /* MigrationHelper.swift */; }; - FD4BB22C2D63FA8600D0DC3D /* (null) in Sources */ = {isa = PBXBuildFile; }; FD4C4E9C2B02E2A300C72199 /* DisplayPictureError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4C4E9B2B02E2A300C72199 /* DisplayPictureError.swift */; }; - FD4C53AF2CC1D62E003B10F4 /* _021_ReworkRecipientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4C53AE2CC1D61E003B10F4 /* _021_ReworkRecipientState.swift */; }; + FD4C53AF2CC1D62E003B10F4 /* _035_ReworkRecipientState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4C53AE2CC1D61E003B10F4 /* _035_ReworkRecipientState.swift */; }; FD52090328B4680F006098F6 /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090228B4680F006098F6 /* RadioButton.swift */; }; FD52090528B4915F006098F6 /* PrivacySettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */; }; FD52CB5A2E12166F00A4DA70 /* OnboardingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52CB592E12166D00A4DA70 /* OnboardingSpec.swift */; }; @@ -730,7 +704,6 @@ FD5E93D12C100FD70038C25A /* FileUploadResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */; }; FD5E93D22C12B0580038C25A /* AppVersionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4383727B3863200C60D73 /* AppVersionResponse.swift */; }; FD61FCF92D308CC9005752DE /* GroupMemberSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD61FCF82D308CC5005752DE /* GroupMemberSpec.swift */; }; - FD61FCFB2D34A5EA005752DE /* _007_SplitSnodeReceivedMessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD61FCFA2D34A5DE005752DE /* _007_SplitSnodeReceivedMessageInfo.swift */; }; FD65318A2AA025C500DFEEAA /* TestDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6531892AA025C500DFEEAA /* TestDependencies.swift */; }; FD65318B2AA025C500DFEEAA /* TestDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6531892AA025C500DFEEAA /* TestDependencies.swift */; }; FD65318C2AA025C500DFEEAA /* TestDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6531892AA025C500DFEEAA /* TestDependencies.swift */; }; @@ -741,7 +714,7 @@ FD6673FD2D77F54600041530 /* ScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6673FC2D77F54400041530 /* ScreenLockViewController.swift */; }; FD6673FF2D77F9C100041530 /* ScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6673FE2D77F9BE00041530 /* ScreenLock.swift */; }; FD6674002D77F9FD00041530 /* ScreenLockWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090828B59411006098F6 /* ScreenLockWindow.swift */; }; - FD66CB2A2BF3449B00268FAB /* SessionSnodeKit.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = FD66CB272BF3449B00268FAB /* SessionSnodeKit.xctestplan */; }; + FD66CB2A2BF3449B00268FAB /* SessionNetworkingKit.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = FD66CB272BF3449B00268FAB /* SessionNetworkingKit.xctestplan */; }; FD6A38E92C2A630E00762359 /* CocoaLumberjackSwift in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A38E82C2A630E00762359 /* CocoaLumberjackSwift */; }; FD6A38EC2C2A63B500762359 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A38EB2C2A63B500762359 /* KeychainSwift */; }; FD6A38EF2C2A641200762359 /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A38EE2C2A641200762359 /* DifferenceKit */; }; @@ -754,19 +727,80 @@ FD6A393B2C2AD3A300762359 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A393A2C2AD3A300762359 /* Nimble */; }; FD6A393D2C2AD3AC00762359 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A393C2C2AD3AC00762359 /* Nimble */; }; FD6A39412C2AD3B600762359 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A39402C2AD3B600762359 /* Nimble */; }; - FD6A7A6D2818C61500035AC1 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */; }; + FD6B928C2E779DCC004463B5 /* FileServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B928B2E779DC8004463B5 /* FileServer.swift */; }; + FD6B928E2E779E99004463B5 /* FileServerEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B928D2E779E95004463B5 /* FileServerEndpoint.swift */; }; + FD6B92902E779EDD004463B5 /* FileServerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B928F2E779EDA004463B5 /* FileServerAPI.swift */; }; + FD6B92922E779FC8004463B5 /* SessionNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92912E779FC6004463B5 /* SessionNetwork.swift */; }; + FD6B92942E77A003004463B5 /* SessionNetworkEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92932E779FFE004463B5 /* SessionNetworkEndpoint.swift */; }; + FD6B92972E77A047004463B5 /* Price.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92962E77A042004463B5 /* Price.swift */; }; + FD6B92992E77A06E004463B5 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92982E77A06C004463B5 /* Token.swift */; }; + FD6B929B2E77A084004463B5 /* NetworkInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B929A2E77A083004463B5 /* NetworkInfo.swift */; }; + FD6B929D2E77A096004463B5 /* Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B929C2E77A095004463B5 /* Info.swift */; }; + FD6B92A32E77A18B004463B5 /* SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92A22E77A189004463B5 /* SnodeAPI.swift */; }; + FD6B92AB2E77A920004463B5 /* SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92A92E77A8F8004463B5 /* SOGS.swift */; }; + FD6B92AC2E77A993004463B5 /* SOGSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */; }; + FD6B92AD2E77A9F1004463B5 /* SOGSError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* SOGSError.swift */; }; + FD6B92AE2E77A9F7004463B5 /* SOGSAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88FA7B726045D100049422F /* SOGSAPI.swift */; }; + FD6B92AF2E77AA03004463B5 /* HTTPQueryParam+SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487E29405994007DCAE5 /* HTTPQueryParam+SOGS.swift */; }; + FD6B92B02E77AA03004463B5 /* Request+SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC132C3677E6009AB976 /* Request+SOGS.swift */; }; + FD6B92B12E77AA03004463B5 /* HTTPHeader+SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487D29405993007DCAE5 /* HTTPHeader+SOGS.swift */; }; + FD6B92B22E77AA03004463B5 /* UpdateTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682228A4C1210069F315 /* UpdateTypes.swift */; }; + FD6B92B32E77AA03004463B5 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; }; + FD6B92B42E77AA11004463B5 /* PinnedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */; }; + FD6B92B52E77AA11004463B5 /* SendDirectMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */; }; + FD6B92B62E77AA11004463B5 /* UserUnbanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */; }; + FD6B92B72E77AA11004463B5 /* UserModeratorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */; }; + FD6B92B82E77AA11004463B5 /* SendSOGSMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387727B5C35400C60D73 /* SendSOGSMessageRequest.swift */; }; + FD6B92B92E77AA11004463B5 /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385C27B4C18900C60D73 /* Room.swift */; }; + FD6B92BA2E77AA11004463B5 /* RoomPollInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */; }; + FD6B92BB2E77AA11004463B5 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; }; + FD6B92BC2E77AA11004463B5 /* ReactionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B81682928B6F1420069F315 /* ReactionResponse.swift */; }; + FD6B92BD2E77AA11004463B5 /* UserBanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A327BB107F00C60D73 /* UserBanRequest.swift */; }; + FD6B92BE2E77AA11004463B5 /* DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C627BB6DF000C60D73 /* DirectMessage.swift */; }; + FD6B92BF2E77AA11004463B5 /* DeleteInboxResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD20C192A0A03AC003898FB /* DeleteInboxResponse.swift */; }; + FD6B92C02E77AA11004463B5 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; }; + FD6B92C12E77AA11004463B5 /* CapabilitiesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386627B4E10E00C60D73 /* CapabilitiesResponse.swift */; }; + FD6B92C22E77AA11004463B5 /* SOGSMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */; }; + FD6B92C62E77AD0F004463B5 /* Crypto+FileServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92C52E77AD0B004463B5 /* Crypto+FileServer.swift */; }; + FD6B92C82E77AD39004463B5 /* Crypto+SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92C72E77AD35004463B5 /* Crypto+SOGS.swift */; }; + FD6B92CD2E77B22D004463B5 /* SOGSMessageSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909027D709CA005DAE71 /* SOGSMessageSpec.swift */; }; + FD6B92CE2E77B234004463B5 /* RoomPollInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */; }; + FD6B92CF2E77B234004463B5 /* RoomSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908627D7047F005DAE71 /* RoomSpec.swift */; }; + FD6B92D02E77B23B004463B5 /* CapabilitiesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92CB2E77B1E5004463B5 /* CapabilitiesResponse.swift */; }; + FD6B92D12E77B253004463B5 /* SendSOGSMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908A27D707F3005DAE71 /* SendSOGSMessageRequestSpec.swift */; }; + FD6B92D22E77B270004463B5 /* SendDirectMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908E27D70938005DAE71 /* SendDirectMessageRequestSpec.swift */; }; + FD6B92D32E77B270004463B5 /* UpdateMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908C27D70905005DAE71 /* UpdateMessageRequestSpec.swift */; }; + FD6B92D42E77B2C7004463B5 /* SOGSAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389927BA002500C60D73 /* SOGSAPISpec.swift */; }; + FD6B92D62E77B55D004463B5 /* SOGSEndpointSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */; }; + FD6B92D72E77B55D004463B5 /* SOGSErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */; }; + FD6B92D82E77B55D004463B5 /* PersonalizationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */; }; + FD6B92DB2E77B597004463B5 /* CryptoSOGSAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92DA2E77B592004463B5 /* CryptoSOGSAPISpec.swift */; }; + FD6B92DE2E77BDE2004463B5 /* Authentication+SOGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92DC2E77BB7E004463B5 /* Authentication+SOGS.swift */; }; + FD6B92E12E77C1E1004463B5 /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92E02E77C1DC004463B5 /* PushNotification.swift */; }; + FD6B92E22E77C21D004463B5 /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; }; + FD6B92E42E77C256004463B5 /* PushNotificationAPI+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92E32E77C250004463B5 /* PushNotificationAPI+SessionMessagingKit.swift */; }; + FD6B92E62E77C5A2004463B5 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D482A16EC20007267C7 /* Service.swift */; }; + FD6B92E72E77C5A2004463B5 /* Request+PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD02CC112C367761009AB976 /* Request+PushNotificationAPI.swift */; }; + FD6B92E82E77C5B7004463B5 /* PushNotificationEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D4F2A16EE50007267C7 /* PushNotificationEndpoint.swift */; }; + FD6B92E92E77C5D1004463B5 /* SubscribeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */; }; + FD6B92EA2E77C5D1004463B5 /* NotificationMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */; }; + FD6B92EB2E77C5D1004463B5 /* AuthenticatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765F32ADE5A0800DC1489 /* AuthenticatedRequest.swift */; }; + FD6B92EF2E77C5D1004463B5 /* UnsubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D552A171FE4007267C7 /* UnsubscribeRequest.swift */; }; + FD6B92F02E77C5D1004463B5 /* SubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */; }; + FD6B92F22E77C5D1004463B5 /* UnsubscribeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */; }; + FD6B92F42E77C61A004463B5 /* ServiceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765F52ADE5BA500DC1489 /* ServiceInfo.swift */; }; + FD6B92F72E77C6D7004463B5 /* Crypto+PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6B92F62E77C6D3004463B5 /* Crypto+PushNotification.swift */; }; + FD6B92F82E77C725004463B5 /* ProcessResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */; }; FD6C67242CF6E72E00B350A7 /* NoopSessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6C67232CF6E72900B350A7 /* NoopSessionCallManager.swift */; }; FD6D9CF92CA152B300F706A8 /* Session+SNUIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754BB2C9B9A8E002A2623 /* Session+SNUIKit.swift */; }; FD6DA9CF2D015B440092085A /* Lucide in Frameworks */ = {isa = PBXBuildFile; productRef = FD6DA9CE2D015B440092085A /* Lucide */; }; FD6DA9D22D0160F10092085A /* Lucide in Frameworks */ = {isa = PBXBuildFile; productRef = FD6DA9D12D0160F10092085A /* Lucide */; }; - FD6DF00B2ACFE40D0084BA4C /* _005_AddSnodeReveivedMessageInfoPrimaryKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6DF00A2ACFE40D0084BA4C /* _005_AddSnodeReveivedMessageInfoPrimaryKey.swift */; }; - FD6E4C8A2A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */; }; FD6F5B5E2E657A24009A8D01 /* CancellationAwareAsyncStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6F5B5D2E657A24009A8D01 /* CancellationAwareAsyncStream.swift */; }; FD6F5B602E657A33009A8D01 /* StreamLifecycleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6F5B5F2E657A32009A8D01 /* StreamLifecycleManager.swift */; }; FD705A92278D051200F16121 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A91278D051200F16121 /* ReusableView.swift */; }; - FD70F25C2DC1F184003729B7 /* _026_MessageDeduplicationTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD70F25B2DC1F176003729B7 /* _026_MessageDeduplicationTable.swift */; }; + FD70F25C2DC1F184003729B7 /* _040_MessageDeduplicationTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD70F25B2DC1F176003729B7 /* _040_MessageDeduplicationTable.swift */; }; FD7115EB28C5D78E00B47552 /* ThreadSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */; }; - FD7115F228C6CB3900B47552 /* _010_AddThreadIdToFTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */; }; + FD7115F228C6CB3900B47552 /* _019_AddThreadIdToFTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F128C6CB3900B47552 /* _019_AddThreadIdToFTS.swift */; }; FD7115F428C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift */; }; FD7115F828C8151C00B47552 /* DisposableBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F728C8151C00B47552 /* DisposableBarButtonItem.swift */; }; FD7115FA28C8153400B47552 /* UIBarButtonItem+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115F928C8153400B47552 /* UIBarButtonItem+Combine.swift */; }; @@ -806,7 +840,7 @@ FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF127BF6BA200510D0C /* Data+Utilities.swift */; }; FD72BDA12BE368C800CF6CF6 /* UIWindowLevel+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD72BDA02BE368C800CF6CF6 /* UIWindowLevel+Utilities.swift */; }; FD72BDA42BE3690B00CF6CF6 /* CryptoSMKSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD72BDA32BE3690B00CF6CF6 /* CryptoSMKSpec.swift */; }; - FD72BDA72BE369DC00CF6CF6 /* CryptoOpenGroupAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupAPISpec.swift */; }; + FD72BDA72BE369DC00CF6CF6 /* CryptoOpenGroupSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupSpec.swift */; }; FD7443402D07A25C00862443 /* PushRegistrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD74433F2D07A25C00862443 /* PushRegistrationManager.swift */; }; FD7443422D07A27E00862443 /* SyncPushTokensJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7443412D07A27E00862443 /* SyncPushTokensJob.swift */; }; FD74434A2D07CA9F00862443 /* Codable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7443492D07CA9F00862443 /* Codable+Utilities.swift */; }; @@ -820,12 +854,12 @@ FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */; }; FD7728962849E7E90018502F /* String+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728952849E7E90018502F /* String+Utilities.swift */; }; FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728972849E8110018502F /* UITableView+ReusableView.swift */; }; - FD778B6429B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */; }; + FD778B6429B189FF001BAC6B /* _028_GenerateInitialUserConfigDumps.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD778B6329B189FF001BAC6B /* _028_GenerateInitialUserConfigDumps.swift */; }; FD78E9EE2DD6D32500D55B50 /* ImageDataManager+Singleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB11A622DD5BDDD00BEF49F /* ImageDataManager+Singleton.swift */; }; FD78E9F22DDA9EA200D55B50 /* MockImageDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9F12DDA9E9B00D55B50 /* MockImageDataManager.swift */; }; FD78E9F42DDABA4F00D55B50 /* AttachmentUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9F32DDABA4200D55B50 /* AttachmentUploader.swift */; }; FD78E9F62DDD43AD00D55B50 /* Mutation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9F52DDD43AB00D55B50 /* Mutation.swift */; }; - FD78E9FA2DDD74D200D55B50 /* _027_MoveSettingsToLibSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9F72DDD742100D55B50 /* _027_MoveSettingsToLibSession.swift */; }; + FD78E9FA2DDD74D200D55B50 /* _042_MoveSettingsToLibSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9F72DDD742100D55B50 /* _042_MoveSettingsToLibSession.swift */; }; FD78E9FD2DDD97F200D55B50 /* Setting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78E9FC2DDD97F000D55B50 /* Setting.swift */; }; FD78EA022DDEBC3200D55B50 /* DebounceTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78EA012DDEBC2C00D55B50 /* DebounceTaskManager.swift */; }; FD78EA042DDEC3C500D55B50 /* MultiTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78EA032DDEC3C000D55B50 /* MultiTaskManager.swift */; }; @@ -833,7 +867,6 @@ FD78EA0A2DDFE45E00D55B50 /* Interaction+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78EA092DDFE45900D55B50 /* Interaction+UI.swift */; }; FD78EA0B2DDFE45E00D55B50 /* Interaction+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78EA092DDFE45900D55B50 /* Interaction+UI.swift */; }; FD78EA0D2DDFEDE200D55B50 /* LibSession+Local.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD78EA0C2DDFEDDF00D55B50 /* LibSession+Local.swift */; }; - FD7F74572BAA9D31006DDFD8 /* _006_DropSnodeCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7F74562BAA9D31006DDFD8 /* _006_DropSnodeCache.swift */; }; FD7F745B2BAAA35E006DDFD8 /* LibSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7F745A2BAAA35E006DDFD8 /* LibSession.swift */; }; FD7F745F2BAAA3B4006DDFD8 /* TypeConversion+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7F745E2BAAA3B4006DDFD8 /* TypeConversion+Utilities.swift */; }; FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; platformFilter = ios; }; @@ -841,8 +874,7 @@ FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; }; FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; }; FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */; }; - FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */; }; - FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */; }; + FD83B9C727CF3F10005E1583 /* CapabilitySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C627CF3F10005E1583 /* CapabilitySpec.swift */; }; FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; }; FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */; }; FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8C283E0B26000E298B /* MessageInputTypes.swift */; }; @@ -854,7 +886,7 @@ FD860CB62D66913F00BBE29C /* ThemePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CB52D66913B00BBE29C /* ThemePreviewView.swift */; }; FD860CB82D66BC9900BBE29C /* AppIconViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CB72D66BC9500BBE29C /* AppIconViewModel.swift */; }; FD860CBA2D66BF2A00BBE29C /* AppIconGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CB92D66BF2300BBE29C /* AppIconGridView.swift */; }; - FD860CBC2D6E7A9F00BBE29C /* _024_FixBustedInteractionVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CBB2D6E7A9400BBE29C /* _024_FixBustedInteractionVariant.swift */; }; + FD860CBC2D6E7A9F00BBE29C /* _038_FixBustedInteractionVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CBB2D6E7A9400BBE29C /* _038_FixBustedInteractionVariant.swift */; }; FD860CBE2D6E7DAA00BBE29C /* DeveloperSettingsViewModel+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD860CBD2D6E7DA000BBE29C /* DeveloperSettingsViewModel+Testing.swift */; }; FD860CC92D6ED2ED00BBE29C /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = FD860CC82D6ED2ED00BBE29C /* DifferenceKit */; }; FD86FDA32BC5020600EC251B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FD86FDA22BC5020600EC251B /* PrivacyInfo.xcprivacy */; }; @@ -877,13 +909,11 @@ FD8A5B252DC05B16004C689B /* Number+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B242DC05B16004C689B /* Number+Utilities.swift */; }; FD8A5B292DC060E2004C689B /* Double+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B282DC060DD004C689B /* Double+Utilities.swift */; }; FD8A5B302DC18D61004C689B /* GeneralCacheSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B2F2DC18D5E004C689B /* GeneralCacheSpec.swift */; }; - FD8A5B322DC191B4004C689B /* _025_DropLegacyClosedGroupKeyPairTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B312DC191AB004C689B /* _025_DropLegacyClosedGroupKeyPairTable.swift */; }; - FD8A5B342DC1A732004C689B /* _008_ResetUserConfigLastHashes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B332DC1A726004C689B /* _008_ResetUserConfigLastHashes.swift */; }; + FD8A5B322DC191B4004C689B /* _039_DropLegacyClosedGroupKeyPairTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B312DC191AB004C689B /* _039_DropLegacyClosedGroupKeyPairTable.swift */; }; FD8ECF7B29340FFD00C0D1BB /* LibSession+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7A29340FFD00C0D1BB /* LibSession+SessionMessagingKit.swift */; }; - FD8ECF7D2934293A00C0D1BB /* _013_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */; }; + FD8ECF7D2934293A00C0D1BB /* _027_SessionUtilChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7C2934293A00C0D1BB /* _027_SessionUtilChanges.swift */; }; FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */; }; FD8FD7622C37B7BD001E38C7 /* Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71162B28E1451400B47552 /* Position.swift */; }; - FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; }; FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; }; FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; }; FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */; }; @@ -933,7 +963,7 @@ FDB3DA8D2E24881B00148F8D /* ImageLoading+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB3DA8C2E24881200148F8D /* ImageLoading+Convenience.swift */; }; FDB4BBC72838B91E00B7C95D /* LinkPreviewError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB4BBC62838B91E00B7C95D /* LinkPreviewError.swift */; }; FDB5DAC12A9443A5002C8721 /* MessageSender+Groups.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAC02A9443A5002C8721 /* MessageSender+Groups.swift */; }; - FDB5DAC72A9447E7002C8721 /* _022_GroupsRebuildChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAC62A9447E7002C8721 /* _022_GroupsRebuildChanges.swift */; }; + FDB5DAC72A9447E7002C8721 /* _036_GroupsRebuildChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAC62A9447E7002C8721 /* _036_GroupsRebuildChanges.swift */; }; FDB5DAD42A9483F3002C8721 /* GroupUpdateInviteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAD32A9483F3002C8721 /* GroupUpdateInviteMessage.swift */; }; FDB5DADA2A95D839002C8721 /* GroupUpdateInfoChangeMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DAD92A95D839002C8721 /* GroupUpdateInfoChangeMessage.swift */; }; FDB5DADC2A95D840002C8721 /* GroupUpdateMemberChangeMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB5DADB2A95D840002C8721 /* GroupUpdateMemberChangeMessage.swift */; }; @@ -957,72 +987,51 @@ FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */; }; FDB7400D28EBEC240094D718 /* DateHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */; }; FDBA8A842D597975007C19C0 /* FailedGroupInvitesAndPromotionsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBA8A832D59796F007C19C0 /* FailedGroupInvitesAndPromotionsJob.swift */; }; - FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */; }; FDBB25E72988BBBE00F1508E /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */; }; FDBEE52E2B6A18B900C143A0 /* UserDefaultsConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBEE52D2B6A18B900C143A0 /* UserDefaultsConfig.swift */; }; FDC0F0042BFECE12002CBFB9 /* TimeUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC0F0032BFECE12002CBFB9 /* TimeUnit.swift */; }; - FDC13D472A16E4CA007267C7 /* SubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */; }; - FDC13D492A16EC20007267C7 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D482A16EC20007267C7 /* Service.swift */; }; - FDC13D4B2A16ECBA007267C7 /* SubscribeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */; }; - FDC13D502A16EE50007267C7 /* PushNotificationAPIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D4F2A16EE50007267C7 /* PushNotificationAPIEndpoint.swift */; }; - FDC13D542A16FF29007267C7 /* LegacyGroupRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D532A16FF29007267C7 /* LegacyGroupRequest.swift */; }; - FDC13D562A171FE4007267C7 /* UnsubscribeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D552A171FE4007267C7 /* UnsubscribeRequest.swift */; }; - FDC13D582A17207D007267C7 /* UnsubscribeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */; }; - FDC13D5A2A1721C5007267C7 /* LegacyNotifyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC13D592A1721C5007267C7 /* LegacyNotifyRequest.swift */; }; FDC1BD662CFD6C4F002CDC71 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC1BD652CFD6C4E002CDC71 /* Config.swift */; }; FDC1BD682CFE6EEB002CDC71 /* DeveloperSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC1BD672CFE6EEA002CDC71 /* DeveloperSettingsViewModel.swift */; }; FDC1BD6A2CFE7B6B002CDC71 /* DirectoryArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC1BD692CFE7B67002CDC71 /* DirectoryArchiver.swift */; }; FDC289472C881A3800020BC2 /* MutableIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC289462C881A3800020BC2 /* MutableIdentifiable.swift */; }; - FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908627D7047F005DAE71 /* RoomSpec.swift */; }; - FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */; }; - FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908A27D707F3005DAE71 /* SendMessageRequestSpec.swift */; }; - FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908C27D70905005DAE71 /* UpdateMessageRequestSpec.swift */; }; - FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2908E27D70938005DAE71 /* SendDirectMessageRequestSpec.swift */; }; - FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909027D709CA005DAE71 /* SOGSMessageSpec.swift */; }; - FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */; }; - FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */; }; - FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */; }; FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; }; FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; }; - FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */; }; - FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; }; - FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */; }; - FDC4382F27B383AF00C60D73 /* LegacyPushServerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */; }; - FDC4385D27B4C18900C60D73 /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385C27B4C18900C60D73 /* Room.swift */; }; - FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */; }; - FDC4386327B4D94E00C60D73 /* SOGSMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */; }; - FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */; }; - FDC4386727B4E10E00C60D73 /* Capabilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4386627B4E10E00C60D73 /* Capabilities.swift */; }; FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; - FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */; }; FDC4389227B9FFC700C60D73 /* SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */; platformFilter = ios; }; - FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */; }; - FDC438A427BB107F00C60D73 /* UserBanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A327BB107F00C60D73 /* UserBanRequest.swift */; }; - FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */; }; - FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */; }; - FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C627BB6DF000C60D73 /* DirectMessage.swift */; }; - FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; }; - FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; }; FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CC27BC641200C60D73 /* Set+Utilities.swift */; }; FDC498B92AC15FE300EDD897 /* AppNotificationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC498B82AC15FE300EDD897 /* AppNotificationAction.swift */; }; FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC6D75F2862B3F600B04575 /* Dependencies.swift */; }; - FDCD2E032A41294E00964D6A /* LegacyGroupOnlyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */; }; FDCDB8E02811007F00352A0C /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */; }; FDD20C162A09E64A003898FB /* GetExpiriesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD20C152A09E64A003898FB /* GetExpiriesRequest.swift */; }; FDD20C182A09E7D3003898FB /* GetExpiriesResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD20C172A09E7D3003898FB /* GetExpiriesResponse.swift */; }; - FDD20C1A2A0A03AC003898FB /* DeleteInboxResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD20C192A0A03AC003898FB /* DeleteInboxResponse.swift */; }; + FDD23ADF2E457CAA0057E853 /* _016_ThemePreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9F828A5F14A003AE748 /* _016_ThemePreferences.swift */; }; + FDD23AE02E457CD40057E853 /* _004_SNK_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _004_SNK_InitialSetupMigration.swift */; }; + FDD23AE12E457CDE0057E853 /* _005_SNK_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A6C2818C61500035AC1 /* _005_SNK_SetupStandardJobs.swift */; }; + FDD23AE22E457CE50057E853 /* _008_SNK_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7A327F40F8100122BE0 /* _008_SNK_YDBToGRDBMigration.swift */; }; + FDD23AE32E457CFE0057E853 /* _010_FlagMessageHashAsDeletedOrInvalid.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD39353528F7C3390084DADA /* _010_FlagMessageHashAsDeletedOrInvalid.swift */; }; + FDD23AE42E458C810057E853 /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6DF00A2ACFE40D0084BA4C /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift */; }; + FDD23AE52E458C940057E853 /* _022_DropSnodeCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7F74562BAA9D31006DDFD8 /* _022_DropSnodeCache.swift */; }; + FDD23AE62E458CAA0057E853 /* _023_SplitSnodeReceivedMessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD61FCFA2D34A5DE005752DE /* _023_SplitSnodeReceivedMessageInfo.swift */; }; + FDD23AE72E458DBC0057E853 /* _001_SUK_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C927F546D900122BE0 /* _001_SUK_InitialSetupMigration.swift */; }; + FDD23AE82E458DD40057E853 /* _002_SUK_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SUK_SetupStandardJobs.swift */; }; + FDD23AE92E458E020057E853 /* _003_SUK_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E627F6A16700122BE0 /* _003_SUK_YDBToGRDBMigration.swift */; }; + FDD23AEA2E458EB00057E853 /* _012_AddJobPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E22988B13800F1508E /* _012_AddJobPriority.swift */; }; + FDD23AEB2E458F4D0057E853 /* _020_AddJobUniqueHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945D9C572D6FDBE7003C4C0C /* _020_AddJobUniqueHash.swift */; }; + FDD23AEC2E458F980057E853 /* _024_ResetUserConfigLastHashes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD8A5B332DC1A726004C689B /* _024_ResetUserConfigLastHashes.swift */; }; + FDD23AED2E4590A10057E853 /* _041_RenameTableSettingToKeyValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FE22D5181F400E8E413 /* _041_RenameTableSettingToKeyValueStore.swift */; }; + FDD23AEE2E459E470057E853 /* _001_SUK_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C927F546D900122BE0 /* _001_SUK_InitialSetupMigration.swift */; }; + FDD23AEF2E459EC90057E853 /* _012_AddJobPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBB25E22988B13800F1508E /* _012_AddJobPriority.swift */; }; + FDD23AF02E459EDD0057E853 /* _020_AddJobUniqueHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945D9C572D6FDBE7003C4C0C /* _020_AddJobUniqueHash.swift */; }; FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; }; FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; }; - FDD82C3F2A205D0A00425F05 /* ProcessResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */; }; - FDDD554E2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDD554D2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift */; }; + FDDD554E2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDD554D2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift */; }; FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */; }; FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */; }; FDE125232A837E4E002DA685 /* MainAppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE125222A837E4E002DA685 /* MainAppContext.swift */; }; FDE33BBC2D5C124900E56F42 /* DispatchTimeInterval+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE33BBB2D5C124300E56F42 /* DispatchTimeInterval+Utilities.swift */; }; - FDE33BBE2D5C3AF100E56F42 /* _023_GroupsExpiredFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE33BBD2D5C3AE800E56F42 /* _023_GroupsExpiredFlag.swift */; }; + FDE33BBE2D5C3AF100E56F42 /* _037_GroupsExpiredFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE33BBD2D5C3AE800E56F42 /* _037_GroupsExpiredFlag.swift */; }; FDE519F72AB7CDC700450C53 /* Result+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE519F62AB7CDC700450C53 /* Result+Utilities.swift */; }; FDE5218E2E03A06B00061B8E /* AttachmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE5218D2E03A06700061B8E /* AttachmentManager.swift */; }; - FDE521902E04CCEB00061B8E /* AVURLAsset+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE5218F2E04CCE600061B8E /* AVURLAsset+Utilities.swift */; }; FDE521942E050B1100061B8E /* DismissCallbackAVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE521932E050B0800061B8E /* DismissCallbackAVPlayerViewController.swift */; }; FDE5219A2E08DBB800061B8E /* ImageLoading+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE521992E08DBB000061B8E /* ImageLoading+Convenience.swift */; }; FDE5219C2E08E76C00061B8E /* SessionAsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE5219B2E08E76600061B8E /* SessionAsyncImage.swift */; }; @@ -1031,9 +1040,14 @@ FDE521A22E0D23AB00061B8E /* ObservableKey+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE521A12E0D23A200061B8E /* ObservableKey+SessionMessagingKit.swift */; }; FDE521A62E0E6C8C00061B8E /* MockNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F5C2CAA28CF00C0B51B /* MockNotificationsManager.swift */; }; FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE6E99729F8E63A00F93C5D /* Accessibility.swift */; }; + FDE71B052E77E1AA0023F5F9 /* ObservableKey+SessionNetworkingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B042E77E1A00023F5F9 /* ObservableKey+SessionNetworkingKit.swift */; }; + FDE71B0B2E79352D0023F5F9 /* DeveloperSettingsGroupsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B0A2E7935250023F5F9 /* DeveloperSettingsGroupsViewModel.swift */; }; + FDE71B0D2E793B250023F5F9 /* DeveloperSettingsProViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE71B0C2E793B1F0023F5F9 /* DeveloperSettingsProViewModel.swift */; }; + FDE71B0F2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit in Resources */ = {isa = PBXBuildFile; fileRef = FDE71B0E2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit */; }; + FDE71B5F2E7A73570023F5F9 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDE71B5E2E7A73560023F5F9 /* StoreKit.framework */; }; FDE7549B2C940108002A2623 /* MessageViewModel+DeletionActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */; }; FDE7549D2C9961A4002A2623 /* CommunityPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7549C2C9961A4002A2623 /* CommunityPoller.swift */; }; - FDE754A12C9A60A6002A2623 /* Crypto+OpenGroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A02C9A60A6002A2623 /* Crypto+OpenGroupAPI.swift */; }; + FDE754A12C9A60A6002A2623 /* Crypto+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A02C9A60A6002A2623 /* Crypto+OpenGroup.swift */; }; FDE754A32C9A8FD1002A2623 /* SwarmPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A22C9A8FD1002A2623 /* SwarmPoller.swift */; }; FDE754A82C9B964D002A2623 /* MessageReceiverGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A42C9B964D002A2623 /* MessageReceiverGroupsSpec.swift */; }; FDE754A92C9B964D002A2623 /* MessageSenderGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754A52C9B964D002A2623 /* MessageSenderGroupsSpec.swift */; }; @@ -1060,7 +1074,7 @@ FDE754DE2C9BAF8A002A2623 /* Crypto+SessionUtilitiesKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754D82C9BAF89002A2623 /* Crypto+SessionUtilitiesKit.swift */; }; FDE754DF2C9BAF8A002A2623 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754D92C9BAF89002A2623 /* KeyPair.swift */; }; FDE754E02C9BAF8A002A2623 /* Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754DA2C9BAF8A002A2623 /* Hex.swift */; }; - FDE754E32C9BAFF4002A2623 /* Crypto+SessionSnodeKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754E12C9BAFF4002A2623 /* Crypto+SessionSnodeKit.swift */; }; + FDE754E32C9BAFF4002A2623 /* Crypto+SessionNetworkingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754E12C9BAFF4002A2623 /* Crypto+SessionNetworkingKit.swift */; }; FDE754E52C9BB012002A2623 /* BezierPathView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754E42C9BB012002A2623 /* BezierPathView.swift */; }; FDE754E72C9BB051002A2623 /* OWSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754E62C9BB051002A2623 /* OWSViewController.swift */; }; FDE754F02C9BB08B002A2623 /* Crypto+Attachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754EC2C9BB08B002A2623 /* Crypto+Attachments.swift */; }; @@ -1069,9 +1083,9 @@ FDE754F82C9BB0B0002A2623 /* UserNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754F32C9BB0AF002A2623 /* UserNotificationConfig.swift */; }; FDE754F92C9BB0B0002A2623 /* NotificationActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754F42C9BB0AF002A2623 /* NotificationActionHandler.swift */; }; FDE754FA2C9BB0B0002A2623 /* NotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754F52C9BB0AF002A2623 /* NotificationPresenter.swift */; }; - FDE754FE2C9BB0D0002A2623 /* Threading+SMK.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754FD2C9BB0D0002A2623 /* Threading+SMK.swift */; }; + FDE754FE2C9BB0D0002A2623 /* Threading+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754FD2C9BB0D0002A2623 /* Threading+SessionMessagingKit.swift */; }; FDE755002C9BB0FA002A2623 /* SessionEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE754FF2C9BB0FA002A2623 /* SessionEnvironment.swift */; }; - FDE755022C9BB122002A2623 /* _011_AddPendingReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755012C9BB122002A2623 /* _011_AddPendingReadReceipts.swift */; }; + FDE755022C9BB122002A2623 /* _025_AddPendingReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755012C9BB122002A2623 /* _025_AddPendingReadReceipts.swift */; }; FDE755052C9BB4EE002A2623 /* BencodeDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755032C9BB4ED002A2623 /* BencodeDecoder.swift */; }; FDE755062C9BB4EE002A2623 /* Bencode.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755042C9BB4ED002A2623 /* Bencode.swift */; }; FDE755182C9BC169002A2623 /* UIAlertAction+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755132C9BC169002A2623 /* UIAlertAction+Utilities.swift */; }; @@ -1082,17 +1096,11 @@ FDE755202C9BC1A6002A2623 /* CacheConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE7551F2C9BC1A6002A2623 /* CacheConfig.swift */; }; FDE755222C9BC1BA002A2623 /* LibSessionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755212C9BC1BA002A2623 /* LibSessionError.swift */; }; FDE755242C9BC1D1002A2623 /* Publisher+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE755232C9BC1D1002A2623 /* Publisher+Utilities.swift */; }; - FDEF57212C3CF03A00131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FDEF57222C3CF03D00131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FDEF57232C3CF04300131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FDEF57242C3CF04700131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FDEF57252C3CF04C00131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; - FDEF57262C3CF05F00131302 /* (null) in Sources */ = {isa = PBXBuildFile; }; FDEF573E2C40F2A100131302 /* GroupUpdateMemberLeftNotificationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDEF573D2C40F2A100131302 /* GroupUpdateMemberLeftNotificationMessage.swift */; }; FDEF57712C44D2D300131302 /* GeoLite2-Country-Blocks-IPv4 in Resources */ = {isa = PBXBuildFile; fileRef = FDEF57702C44D2D300131302 /* GeoLite2-Country-Blocks-IPv4 */; }; FDF01FAD2A9ECC4200CAF969 /* SingletonConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF01FAC2A9ECC4200CAF969 /* SingletonConfig.swift */; }; FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */; }; - FDF0B7422804EA4F004C14C5 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7412804EA4F004C14C5 /* _002_SetupStandardJobs.swift */; }; + FDF0B7422804EA4F004C14C5 /* _007_SMK_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7412804EA4F004C14C5 /* _007_SMK_SetupStandardJobs.swift */; }; FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B74828060D13004C14C5 /* QuotedReplyModel.swift */; }; FDF0B74B28061F7A004C14C5 /* InteractionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B74A28061F7A004C14C5 /* InteractionAttachment.swift */; }; FDF0B7512807BA56004C14C5 /* NotificationsManagerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7502807BA56004C14C5 /* NotificationsManagerType.swift */; }; @@ -1106,14 +1114,11 @@ FDF2220F281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */; }; FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */; }; FDF2F0222DAE1AF500491E8A /* MessageReceiver+LegacyClosedGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF2F0212DAE1AEF00491E8A /* MessageReceiver+LegacyClosedGroups.swift */; }; - FDF40CDE2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */; }; + FDF40CDE2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF40CDD2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift */; }; FDF71EA32B072C2800A8D6B5 /* LibSessionMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF71EA22B072C2800A8D6B5 /* LibSessionMessage.swift */; }; FDF71EA52B07363500A8D6B5 /* MessageReceiver+LibSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF71EA42B07363500A8D6B5 /* MessageReceiver+LibSession.swift */; }; - FDF8487F29405994007DCAE5 /* HTTPHeader+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487D29405993007DCAE5 /* HTTPHeader+OpenGroup.swift */; }; - FDF8488029405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8487E29405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift */; }; FDF8488929405B27007DCAE5 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */; }; FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */; }; - FDF8489429405C1B007DCAE5 /* SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */; }; FDF848BC29405C5A007DCAE5 /* SnodeRecursiveResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489A29405C5A007DCAE5 /* SnodeRecursiveResponse.swift */; }; FDF848BD29405C5A007DCAE5 /* GetMessagesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489B29405C5A007DCAE5 /* GetMessagesRequest.swift */; }; FDF848BF29405C5A007DCAE5 /* SnodeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489D29405C5A007DCAE5 /* SnodeResponse.swift */; }; @@ -1149,14 +1154,13 @@ FDF848F329413DB0007DCAE5 /* ImagePickerHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */; }; FDF848F529413EEC007DCAE5 /* SessionCell+Styling.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F429413EEC007DCAE5 /* SessionCell+Styling.swift */; }; FDF848F729414477007DCAE5 /* CurrentUserPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF848F629414477007DCAE5 /* CurrentUserPoller.swift */; }; - FDFBB74D2A1F3C4E00CA7350 /* NotificationMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */; }; FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5D22553860900C340D1 /* String+Trimming.swift */; }; FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FDFDE124282D04F20098B17F /* MediaDismissAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE123282D04F20098B17F /* MediaDismissAnimationController.swift */; }; FDFDE126282D05380098B17F /* MediaInteractiveDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */; }; FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; }; FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */; }; - FDFE75B12ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */; }; + FDFE75B12ABD2D2400655640 /* _030_MakeBrokenProfileTimestampsNullable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFE75B02ABD2D2400655640 /* _030_MakeBrokenProfileTimestampsNullable.swift */; }; FDFE75B42ABD46B600655640 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; }; FDFE75B52ABD46B700655640 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; }; FDFF9FDF2A787F57005E0628 /* JSONEncoder+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */; }; @@ -1363,7 +1367,7 @@ files = ( C3C2A681255388CC00C340D1 /* SessionUtilitiesKit.framework in Embed Frameworks */, C33FD9B3255A548A00E217F9 /* SignalUtilitiesKit.framework in Embed Frameworks */, - C3C2A5A7255385C100C340D1 /* SessionSnodeKit.framework in Embed Frameworks */, + C3C2A5A7255385C100C340D1 /* SessionNetworkingKit.framework in Embed Frameworks */, C331FF232558F9D300070591 /* SessionUIKit.framework in Embed Frameworks */, C3C2A6F825539DE700C340D1 /* SessionMessagingKit.framework in Embed Frameworks */, ); @@ -1465,7 +1469,7 @@ 7B4EF2592934743000CB351D /* SessionTableViewTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTableViewTitleView.swift; sourceTree = ""; }; 7B50D64C28AC7CF80086CCEC /* silence.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = silence.aiff; sourceTree = ""; }; 7B5233C32900E90F00F8F375 /* SessionLabelCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionLabelCarouselView.swift; sourceTree = ""; }; - 7B5233C5290636D700F8F375 /* _018_DisappearingMessagesConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _018_DisappearingMessagesConfiguration.swift; sourceTree = ""; }; + 7B5233C5290636D700F8F375 /* _032_DisappearingMessagesConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _032_DisappearingMessagesConfiguration.swift; sourceTree = ""; }; 7B5802982AAEF1B50050EEB1 /* OpenGroupInvitationView_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupInvitationView_SwiftUI.swift; sourceTree = ""; }; 7B7037422834B81F000DCF35 /* ReactionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionContainerView.swift; sourceTree = ""; }; 7B7037442834BCC0000DCF35 /* ReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = ""; }; @@ -1474,7 +1478,7 @@ 7B7CB18F270FB2150079FF93 /* MiniCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniCallView.swift; sourceTree = ""; }; 7B7CB191271508AD0079FF93 /* CallRingTonePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallRingTonePlayer.swift; sourceTree = ""; }; 7B81682228A4C1210069F315 /* UpdateTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateTypes.swift; sourceTree = ""; }; - 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _007_HomeQueryOptimisationIndexes.swift; sourceTree = ""; }; + 7B81682728B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _015_HomeQueryOptimisationIndexes.swift; sourceTree = ""; }; 7B81682928B6F1420069F315 /* ReactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionResponse.swift; sourceTree = ""; }; 7B81682B28B72F480069F315 /* PendingChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingChange.swift; sourceTree = ""; }; 7B81FB582AB01AA8002FB267 /* LoadingIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicatorView.swift; sourceTree = ""; }; @@ -1497,7 +1501,7 @@ 7BA68908272A27BE00EFC32F /* SessionCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCall.swift; sourceTree = ""; }; 7BA6890C27325CCC00EFC32F /* SessionCallManager+CXCallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXCallController.swift"; sourceTree = ""; }; 7BA6890E27325CE300EFC32F /* SessionCallManager+CXProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCallManager+CXProvider.swift"; sourceTree = ""; }; - 7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _009_OpenGroupPermission.swift; sourceTree = ""; }; + 7BAA7B6528D2DE4700AE1489 /* _018_OpenGroupPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _018_OpenGroupPermission.swift; sourceTree = ""; }; 7BAADFCB27B0EF23007BCF92 /* CallVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVideoView.swift; sourceTree = ""; }; 7BAADFCD27B215FE007BCF92 /* UIView+Draggable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Draggable.swift"; sourceTree = ""; }; 7BAF54CC27ACCEEC003D12F8 /* GlobalSearchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalSearchViewController.swift; sourceTree = ""; }; @@ -1525,7 +1529,6 @@ 9409433F2C7ED62300D9D2E0 /* StartupError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupError.swift; sourceTree = ""; }; 941375BA2D5184B60058F244 /* HTTPHeader+SessionNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HTTPHeader+SessionNetwork.swift"; sourceTree = ""; }; 941375BC2D5195F30058F244 /* KeyValueStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueStore.swift; sourceTree = ""; }; - 941375BE2D5196D10058F244 /* Number+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Number+Utilities.swift"; sourceTree = ""; }; 9422567D2C23F8BB00C0FDBF /* StartConversationScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StartConversationScreen.swift; sourceTree = ""; }; 9422567E2C23F8BB00C0FDBF /* NewMessageScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewMessageScreen.swift; sourceTree = ""; }; 9422567F2C23F8BB00C0FDBF /* InviteAFriendScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InviteAFriendScreen.swift; sourceTree = ""; }; @@ -1549,19 +1552,18 @@ 94367C422C6C828500814252 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; 943C6D812B75E061004ACE64 /* Message+DisappearingMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+DisappearingMessages.swift"; sourceTree = ""; }; 943C6D832B86B5F1004ACE64 /* Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; - 945D9C572D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _005_AddJobUniqueHash.swift; sourceTree = ""; }; + 945D9C572D6FDBE7003C4C0C /* _020_AddJobUniqueHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _020_AddJobUniqueHash.swift; sourceTree = ""; }; 9471CAA72CACFB4E00090FB7 /* GenerateLicenses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateLicenses.swift; sourceTree = ""; }; 9473386D2BDF5F3E00B9E169 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; 9479981B2DD44AC5008F5CD5 /* ThreadNotificationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadNotificationSettingsViewModel.swift; sourceTree = ""; }; 947AD68F2C8968FF000B2730 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 947D7FCF2D509FC900E8E413 /* SessionNetworkAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNetworkAPI.swift; sourceTree = ""; }; - 947D7FD02D509FC900E8E413 /* SessionNetworkAPI+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkAPI+Database.swift"; sourceTree = ""; }; - 947D7FD12D509FC900E8E413 /* SessionNetworkAPI+Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkAPI+Models.swift"; sourceTree = ""; }; - 947D7FD22D509FC900E8E413 /* SessionNetworkAPI+Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkAPI+Network.swift"; sourceTree = ""; }; + 947D7FD02D509FC900E8E413 /* KeyValueStore+SessionNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyValueStore+SessionNetwork.swift"; sourceTree = ""; }; + 947D7FD22D509FC900E8E413 /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; 947D7FD92D5180F200E8E413 /* SessionNetworkScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNetworkScreen.swift; sourceTree = ""; }; 947D7FDA2D5180F200E8E413 /* SessionNetworkScreen+Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkScreen+Models.swift"; sourceTree = ""; }; 947D7FDB2D5180F200E8E413 /* SessionNetworkScreen+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkScreen+ViewModel.swift"; sourceTree = ""; }; - 947D7FE22D5181F400E8E413 /* _006_RenameTableSettingToKeyValueStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _006_RenameTableSettingToKeyValueStore.swift; sourceTree = ""; }; + 947D7FE22D5181F400E8E413 /* _041_RenameTableSettingToKeyValueStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _041_RenameTableSettingToKeyValueStore.swift; sourceTree = ""; }; 947D7FE42D51837200E8E413 /* ArrowCapsule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowCapsule.swift; sourceTree = ""; }; 947D7FE52D51837200E8E413 /* PopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverView.swift; sourceTree = ""; }; 947D7FE62D51837200E8E413 /* Text+CopyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Text+CopyButton.swift"; sourceTree = ""; }; @@ -1579,7 +1581,7 @@ 94C58AC82D2E036E00609195 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = ""; }; 94CD95BA2E08D9D40097754D /* SessionProBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionProBadge.swift; sourceTree = ""; }; 94CD95BC2E0908340097754D /* LibSession+Pro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LibSession+Pro.swift"; sourceTree = ""; }; - 94CD95C02E0CBF1C0097754D /* _029_AddProMessageFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _029_AddProMessageFlag.swift; sourceTree = ""; }; + 94CD95C02E0CBF1C0097754D /* _044_AddProMessageFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _044_AddProMessageFlag.swift; sourceTree = ""; }; 94CD962A2E1B85920097754D /* InputTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputTextView.swift; sourceTree = ""; }; 94CD962B2E1B85920097754D /* InputViewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputViewButton.swift; sourceTree = ""; }; 94CD962F2E1B88430097754D /* CGRect+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGRect+Utilities.swift"; sourceTree = ""; }; @@ -1626,7 +1628,7 @@ B879D44A247E1D9200DB3608 /* PathStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathStatusView.swift; sourceTree = ""; }; B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = ""; }; B886B4A82398BA1500211ABE /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = ""; }; - B88FA7B726045D100049422F /* OpenGroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPI.swift; sourceTree = ""; }; + B88FA7B726045D100049422F /* SOGSAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSAPI.swift; sourceTree = ""; }; B88FA7F1260C3EB10049422F /* OpenGroupSuggestionGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSuggestionGrid.swift; sourceTree = ""; }; B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQRCodeWrapperVC.swift; sourceTree = ""; }; B894D0742339EDCF00B4D94D /* NukeDataModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeDataModal.swift; sourceTree = ""; }; @@ -1747,13 +1749,11 @@ C3AAFFF125AE99710089E6DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C3ADC66026426688005F1414 /* ShareNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareNavController.swift; sourceTree = ""; }; C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FixedWidthInteger+BigEndian.swift"; sourceTree = ""; }; - C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionSnodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C3C2A5A1255385C100C340D1 /* SessionSnodeKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionSnodeKit.h; sourceTree = ""; }; + C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionNetworkingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C3C2A5A1255385C100C340D1 /* SessionNetworkingKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionNetworkingKit.h; sourceTree = ""; }; C3C2A5A2255385C100C340D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C3C2A5B9255385ED00C340D1 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; C3C2A5CE2553860700C340D1 /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; C3C2A5D22553860900C340D1 /* String+Trimming.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Trimming.swift"; sourceTree = ""; }; - C3C2A5D42553860A00C340D1 /* Threading+SSK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Threading+SSK.swift"; sourceTree = ""; }; C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Utilities.swift"; sourceTree = ""; }; C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1807,10 +1807,10 @@ FD01504D2CA243E7005B08A1 /* TypeConversionUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeConversionUtilitiesSpec.swift; sourceTree = ""; }; FD0150572CA27DEE005B08A1 /* ScrollableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableLabel.swift; sourceTree = ""; }; FD02CC112C367761009AB976 /* Request+PushNotificationAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Request+PushNotificationAPI.swift"; sourceTree = ""; }; - FD02CC132C3677E6009AB976 /* Request+OpenGroupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Request+OpenGroupAPI.swift"; sourceTree = ""; }; + FD02CC132C3677E6009AB976 /* Request+SOGS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Request+SOGS.swift"; sourceTree = ""; }; FD02CC152C3681EF009AB976 /* RevokeSubaccountResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RevokeSubaccountResponse.swift; sourceTree = ""; }; FD05593C2DFA3A2200DC48CE /* VoipPayloadKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipPayloadKey.swift; sourceTree = ""; }; - FD05594D2E012D1A00DC48CE /* _028_RenameAttachments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _028_RenameAttachments.swift; sourceTree = ""; }; + FD05594D2E012D1A00DC48CE /* _043_RenameAttachments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _043_RenameAttachments.swift; sourceTree = ""; }; FD0559542E026CC900DC48CE /* ObservingDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingDatabase.swift; sourceTree = ""; }; FD0606C22BCE13ED00C3816E /* MessageRequestFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestFooterView.swift; sourceTree = ""; }; FD0969F82A69FFE700C5C365 /* Mocked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mocked.swift; sourceTree = ""; }; @@ -1829,7 +1829,7 @@ FD09799827FFC1A300936362 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = ""; }; FD09799A27FFC82D00936362 /* Quote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quote.swift; sourceTree = ""; }; FD09B7E228865FDA00ED0B66 /* HighlightMentionBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightMentionBackgroundView.swift; sourceTree = ""; }; - FD09B7E4288670BB00ED0B66 /* _008_EmojiReacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _008_EmojiReacts.swift; sourceTree = ""; }; + FD09B7E4288670BB00ED0B66 /* _017_EmojiReacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _017_EmojiReacts.swift; sourceTree = ""; }; FD09B7E6288670FD00ED0B66 /* Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reaction.swift; sourceTree = ""; }; FD09C5E528260FF9000CE219 /* MediaGalleryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryViewModel.swift; sourceTree = ""; }; FD09C5E728264937000CE219 /* MediaDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDetailViewController.swift; sourceTree = ""; }; @@ -1841,7 +1841,6 @@ FD10AF0B2AF32B9A007709E5 /* SessionListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionListViewModel.swift; sourceTree = ""; }; FD10AF112AF85D11007709E5 /* Feature+ServiceNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Feature+ServiceNetwork.swift"; sourceTree = ""; }; FD11E22F2CA4F498001BAF58 /* DestinationSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestinationSpec.swift; sourceTree = ""; }; - FD12A83C2AD63BCC00EEBA0D /* EditableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableState.swift; sourceTree = ""; }; FD12A83E2AD63BDF00EEBA0D /* Navigatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigatable.swift; sourceTree = ""; }; FD12A8402AD63BEA00EEBA0D /* NavigatableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatableState.swift; sourceTree = ""; }; FD12A8422AD63BF600EEBA0D /* ObservableTableSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableTableSource.swift; sourceTree = ""; }; @@ -1849,19 +1848,17 @@ FD12A8462AD63C3400EEBA0D /* PagedObservationSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedObservationSource.swift; sourceTree = ""; }; FD12A8482AD63C4700EEBA0D /* SessionNavItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNavItem.swift; sourceTree = ""; }; FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfilePictureView+Convenience.swift"; sourceTree = ""; }; - FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; - FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; - FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; - FD17D7A327F40F8100122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; + FD17D79527F3E04600122BE0 /* _006_SMK_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _006_SMK_InitialSetupMigration.swift; sourceTree = ""; }; + FD17D79827F40AB800122BE0 /* _009_SMK_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _009_SMK_YDBToGRDBMigration.swift; sourceTree = ""; }; + FD17D79F27F40CC800122BE0 /* _004_SNK_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_SNK_InitialSetupMigration.swift; sourceTree = ""; }; + FD17D7A327F40F8100122BE0 /* _008_SNK_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _008_SNK_YDBToGRDBMigration.swift; sourceTree = ""; }; FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessageInfo.swift; sourceTree = ""; }; FD17D7AF27F4225C00122BE0 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = ""; }; FD17D7B727F51ECA00122BE0 /* Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Migration.swift; sourceTree = ""; }; - FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetMigrations.swift; sourceTree = ""; }; FD17D7BE27F51F8200122BE0 /* ColumnExpressible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnExpressible.swift; sourceTree = ""; }; - FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatabaseMigrator+Utilities.swift"; sourceTree = ""; }; - FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; + FD17D7C927F546D900122BE0 /* _001_SUK_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_SUK_InitialSetupMigration.swift; sourceTree = ""; }; FD17D7E427F6A09900122BE0 /* Identity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = ""; }; - FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; + FD17D7E627F6A16700122BE0 /* _003_SUK_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_SUK_YDBToGRDBMigration.swift; sourceTree = ""; }; FD19363B2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ResponseInfo+SnodeAPI.swift"; sourceTree = ""; }; FD19363E2ACA66DE004BCF0F /* DatabaseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseSpec.swift; sourceTree = ""; }; FD1A553D2E14BE0E003761E4 /* PagedData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedData.swift; sourceTree = ""; }; @@ -1869,7 +1866,7 @@ FD1A55422E179AE6003761E4 /* ObservableKeyEvent+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObservableKeyEvent+Utilities.swift"; sourceTree = ""; }; FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistableRecord+Utilities.swift"; sourceTree = ""; }; FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Utilities.swift"; sourceTree = ""; }; - FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _015_BlockCommunityMessageRequests.swift; sourceTree = ""; }; + FD1D732D2A86114600E3F410 /* _029_BlockCommunityMessageRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _029_BlockCommunityMessageRequests.swift; sourceTree = ""; }; FD2272532C32911A004D8A6C /* SendReadReceiptsJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendReadReceiptsJob.swift; sourceTree = ""; }; FD2272542C32911A004D8A6C /* GroupLeavingJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupLeavingJob.swift; sourceTree = ""; }; FD2272552C32911A004D8A6C /* CheckForAppUpdatesJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckForAppUpdatesJob.swift; sourceTree = ""; }; @@ -1940,7 +1937,7 @@ FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronousStorage.swift; sourceTree = ""; }; FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryInterfaceRequest+Utilities.swift"; sourceTree = ""; }; FD336F562CAA28CF00C0B51B /* CommonSMKMockExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonSMKMockExtensions.swift; sourceTree = ""; }; - FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomArgSummaryDescribable+SMK.swift"; sourceTree = ""; }; + FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomArgSummaryDescribable+SessionMessagingKit.swift"; sourceTree = ""; }; FD336F582CAA28CF00C0B51B /* MockCommunityPollerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCommunityPollerCache.swift; sourceTree = ""; }; FD336F592CAA28CF00C0B51B /* MockDisplayPictureCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDisplayPictureCache.swift; sourceTree = ""; }; FD336F5A2CAA28CF00C0B51B /* MockGroupPollerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGroupPollerCache.swift; sourceTree = ""; }; @@ -1951,8 +1948,8 @@ FD336F5F2CAA28CF00C0B51B /* MockSwarmPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSwarmPoller.swift; sourceTree = ""; }; FD336F6B2CAA29C200C0B51B /* CommunityPollerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityPollerSpec.swift; sourceTree = ""; }; FD336F6E2CAA37CB00C0B51B /* MockCommunityPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCommunityPoller.swift; sourceTree = ""; }; - FD3559452CC1FF140088F2A9 /* _020_AddMissingWhisperFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _020_AddMissingWhisperFlag.swift; sourceTree = ""; }; - FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _012_AddFTSIfNeeded.swift; sourceTree = ""; }; + FD3559452CC1FF140088F2A9 /* _034_AddMissingWhisperFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _034_AddMissingWhisperFlag.swift; sourceTree = ""; }; + FD368A6729DE8F9B000DBF1E /* _026_AddFTSIfNeeded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _026_AddFTSIfNeeded.swift; sourceTree = ""; }; FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Utilities.swift"; sourceTree = ""; }; FD3765DE2AD8F03100DC1489 /* MockSnodeAPICache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSnodeAPICache.swift; sourceTree = ""; }; FD3765E12AD8F53B00DC1489 /* CommonSSKMockExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonSSKMockExtensions.swift; sourceTree = ""; }; @@ -1973,7 +1970,7 @@ FD37E9DA28A244E9003AE748 /* ThemeMessagePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeMessagePreviewView.swift; sourceTree = ""; }; FD37E9DC28A384EB003AE748 /* PrimaryColorSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryColorSelectionView.swift; sourceTree = ""; }; FD37E9F528A5F106003AE748 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; - FD37E9F828A5F14A003AE748 /* _001_ThemePreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_ThemePreferences.swift; sourceTree = ""; }; + FD37E9F828A5F14A003AE748 /* _016_ThemePreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _016_ThemePreferences.swift; sourceTree = ""; }; FD37E9FE28A5F2CD003AE748 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; FD37EA0028A60473003AE748 /* UIKit+Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKit+Theme.swift"; sourceTree = ""; }; FD37EA0228A9FDCC003AE748 /* HelpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpViewModel.swift; sourceTree = ""; }; @@ -1981,13 +1978,13 @@ FD37EA0628AA2CCA003AE748 /* SessionTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTableViewController.swift; sourceTree = ""; }; FD37EA0828AA2D27003AE748 /* SessionTableViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTableViewModel.swift; sourceTree = ""; }; FD37EA0A28AB12E2003AE748 /* SessionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCell.swift; sourceTree = ""; }; - FD37EA0C28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _005_FixDeletedMessageReadState.swift; sourceTree = ""; }; - FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _006_FixHiddenModAdminSupport.swift; sourceTree = ""; }; + FD37EA0C28AB2A45003AE748 /* _013_FixDeletedMessageReadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_FixDeletedMessageReadState.swift; sourceTree = ""; }; + FD37EA0E28AB3330003AE748 /* _014_FixHiddenModAdminSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _014_FixHiddenModAdminSupport.swift; sourceTree = ""; }; FD37EA1428AB42CB003AE748 /* IdentitySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentitySpec.swift; sourceTree = ""; }; FD37EA1628AC5605003AE748 /* NotificationContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentViewModel.swift; sourceTree = ""; }; FD37EA1828AC5CCA003AE748 /* NotificationSoundViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundViewModel.swift; sourceTree = ""; }; FD39352B28F382920084DADA /* VersionFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionFooterView.swift; sourceTree = ""; }; - FD39353528F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_FlagMessageHashAsDeletedOrInvalid.swift; sourceTree = ""; }; + FD39353528F7C3390084DADA /* _010_FlagMessageHashAsDeletedOrInvalid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _010_FlagMessageHashAsDeletedOrInvalid.swift; sourceTree = ""; }; FD3937072E4AD3F800571F17 /* NoopDependency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopDependency.swift; sourceTree = ""; }; FD39370B2E4D7BBE00571F17 /* DocumentPickerHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPickerHandler.swift; sourceTree = ""; }; FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerViewModel.swift; sourceTree = ""; }; @@ -2005,7 +2002,7 @@ FD428B182B4B576F006D0888 /* AppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = ""; }; FD428B1A2B4B6098006D0888 /* Notifications+Lifecycle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notifications+Lifecycle.swift"; sourceTree = ""; }; FD428B1E2B4B758B006D0888 /* AppReadiness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReadiness.swift; sourceTree = ""; }; - FD428B222B4B9969006D0888 /* _017_RebuildFTSIfNeeded_2_4_5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _017_RebuildFTSIfNeeded_2_4_5.swift; sourceTree = ""; }; + FD428B222B4B9969006D0888 /* _031_RebuildFTSIfNeeded_2_4_5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _031_RebuildFTSIfNeeded_2_4_5.swift; sourceTree = ""; }; FD42ECCD2E287CD1002D03EA /* ThemeColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeColor.swift; sourceTree = ""; }; FD42ECCF2E289257002D03EA /* ThemeLinearGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeLinearGradient.swift; sourceTree = ""; }; FD42ECD12E3071DC002D03EA /* ThemeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeText.swift; sourceTree = ""; }; @@ -2024,7 +2021,7 @@ FD4B200D283492210034334B /* InsetLockableTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLockableTableView.swift; sourceTree = ""; }; FD4BB22A2D63F20600D0DC3D /* MigrationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationHelper.swift; sourceTree = ""; }; FD4C4E9B2B02E2A300C72199 /* DisplayPictureError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayPictureError.swift; sourceTree = ""; }; - FD4C53AE2CC1D61E003B10F4 /* _021_ReworkRecipientState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _021_ReworkRecipientState.swift; sourceTree = ""; }; + FD4C53AE2CC1D61E003B10F4 /* _035_ReworkRecipientState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _035_ReworkRecipientState.swift; sourceTree = ""; }; FD52090228B4680F006098F6 /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettingsViewModel.swift; sourceTree = ""; }; FD52090628B49738006098F6 /* ConfirmationModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationModal.swift; sourceTree = ""; }; @@ -2047,24 +2044,42 @@ FD5CE3442A3C5D96001A6DE3 /* DecryptExportedKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecryptExportedKey.swift; sourceTree = ""; }; FD5D201D27B0D87C00FEA984 /* SessionId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionId.swift; sourceTree = ""; }; FD61FCF82D308CC5005752DE /* GroupMemberSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberSpec.swift; sourceTree = ""; }; - FD61FCFA2D34A5DE005752DE /* _007_SplitSnodeReceivedMessageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _007_SplitSnodeReceivedMessageInfo.swift; sourceTree = ""; }; + FD61FCFA2D34A5DE005752DE /* _023_SplitSnodeReceivedMessageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _023_SplitSnodeReceivedMessageInfo.swift; sourceTree = ""; }; FD6531892AA025C500DFEEAA /* TestDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDependencies.swift; sourceTree = ""; }; FD6673FC2D77F54400041530 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = ""; }; FD6673FE2D77F9BE00041530 /* ScreenLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLock.swift; sourceTree = ""; }; - FD66CB272BF3449B00268FAB /* SessionSnodeKit.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SessionSnodeKit.xctestplan; sourceTree = ""; }; + FD66CB272BF3449B00268FAB /* SessionNetworkingKit.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SessionNetworkingKit.xctestplan; sourceTree = ""; }; FD66CB2B2BF344C600268FAB /* SessionMessageKit.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SessionMessageKit.xctestplan; sourceTree = ""; }; FD6A38F02C2A66B100762359 /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = ""; }; - FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; + FD6A7A6C2818C61500035AC1 /* _005_SNK_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _005_SNK_SetupStandardJobs.swift; sourceTree = ""; }; + FD6B928B2E779DC8004463B5 /* FileServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServer.swift; sourceTree = ""; }; + FD6B928D2E779E95004463B5 /* FileServerEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServerEndpoint.swift; sourceTree = ""; }; + FD6B928F2E779EDA004463B5 /* FileServerAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileServerAPI.swift; sourceTree = ""; }; + FD6B92912E779FC6004463B5 /* SessionNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNetwork.swift; sourceTree = ""; }; + FD6B92932E779FFE004463B5 /* SessionNetworkEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNetworkEndpoint.swift; sourceTree = ""; }; + FD6B92962E77A042004463B5 /* Price.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Price.swift; sourceTree = ""; }; + FD6B92982E77A06C004463B5 /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; + FD6B929A2E77A083004463B5 /* NetworkInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInfo.swift; sourceTree = ""; }; + FD6B929C2E77A095004463B5 /* Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Info.swift; sourceTree = ""; }; + FD6B92A22E77A189004463B5 /* SnodeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeAPI.swift; sourceTree = ""; }; + FD6B92A92E77A8F8004463B5 /* SOGS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGS.swift; sourceTree = ""; }; + FD6B92C52E77AD0B004463B5 /* Crypto+FileServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+FileServer.swift"; sourceTree = ""; }; + FD6B92C72E77AD35004463B5 /* Crypto+SOGS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+SOGS.swift"; sourceTree = ""; }; + FD6B92CB2E77B1E5004463B5 /* CapabilitiesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitiesResponse.swift; sourceTree = ""; }; + FD6B92DA2E77B592004463B5 /* CryptoSOGSAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSOGSAPISpec.swift; sourceTree = ""; }; + FD6B92DC2E77BB7E004463B5 /* Authentication+SOGS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Authentication+SOGS.swift"; sourceTree = ""; }; + FD6B92E02E77C1DC004463B5 /* PushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotification.swift; sourceTree = ""; }; + FD6B92E32E77C250004463B5 /* PushNotificationAPI+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PushNotificationAPI+SessionMessagingKit.swift"; sourceTree = ""; }; + FD6B92F62E77C6D3004463B5 /* Crypto+PushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+PushNotification.swift"; sourceTree = ""; }; FD6C67232CF6E72900B350A7 /* NoopSessionCallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopSessionCallManager.swift; sourceTree = ""; }; - FD6DF00A2ACFE40D0084BA4C /* _005_AddSnodeReveivedMessageInfoPrimaryKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _005_AddSnodeReveivedMessageInfoPrimaryKey.swift; sourceTree = ""; }; - FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyUnsubscribeRequest.swift; sourceTree = ""; }; + FD6DF00A2ACFE40D0084BA4C /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _021_AddSnodeReveivedMessageInfoPrimaryKey.swift; sourceTree = ""; }; FD6F5B5D2E657A24009A8D01 /* CancellationAwareAsyncStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancellationAwareAsyncStream.swift; sourceTree = ""; }; FD6F5B5F2E657A32009A8D01 /* StreamLifecycleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamLifecycleManager.swift; sourceTree = ""; }; FD705A91278D051200F16121 /* ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = ""; }; - FD70F25B2DC1F176003729B7 /* _026_MessageDeduplicationTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _026_MessageDeduplicationTable.swift; sourceTree = ""; }; + FD70F25B2DC1F176003729B7 /* _040_MessageDeduplicationTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _040_MessageDeduplicationTable.swift; sourceTree = ""; }; FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSettingsViewModel.swift; sourceTree = ""; }; FD7115EF28C5D7DE00B47552 /* SessionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHeaderView.swift; sourceTree = ""; }; - FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _010_AddThreadIdToFTS.swift; sourceTree = ""; }; + FD7115F128C6CB3900B47552 /* _019_AddThreadIdToFTS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _019_AddThreadIdToFTS.swift; sourceTree = ""; }; FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDisappearingMessagesSettingsViewModel.swift; sourceTree = ""; }; FD7115F728C8151C00B47552 /* DisposableBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisposableBarButtonItem.swift; sourceTree = ""; }; FD7115F928C8153400B47552 /* UIBarButtonItem+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Combine.swift"; sourceTree = ""; }; @@ -2098,7 +2113,7 @@ FD716E7028505E5100C96BF4 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = ""; }; FD72BDA02BE368C800CF6CF6 /* UIWindowLevel+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIWindowLevel+Utilities.swift"; sourceTree = ""; }; FD72BDA32BE3690B00CF6CF6 /* CryptoSMKSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSMKSpec.swift; sourceTree = ""; }; - FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoOpenGroupAPISpec.swift; sourceTree = ""; }; + FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoOpenGroupSpec.swift; sourceTree = ""; }; FD74433F2D07A25C00862443 /* PushRegistrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRegistrationManager.swift; sourceTree = ""; }; FD7443412D07A27E00862443 /* SyncPushTokensJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPushTokensJob.swift; sourceTree = ""; }; FD7443452D07CA9F00862443 /* CGFloat+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Utilities.swift"; sourceTree = ""; }; @@ -2109,18 +2124,18 @@ FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModelSpec.swift; sourceTree = ""; }; FD7728952849E7E90018502F /* String+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Utilities.swift"; sourceTree = ""; }; FD7728972849E8110018502F /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = ""; }; - FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _014_GenerateInitialUserConfigDumps.swift; sourceTree = ""; }; + FD778B6329B189FF001BAC6B /* _028_GenerateInitialUserConfigDumps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _028_GenerateInitialUserConfigDumps.swift; sourceTree = ""; }; FD78E9F12DDA9E9B00D55B50 /* MockImageDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockImageDataManager.swift; sourceTree = ""; }; FD78E9F32DDABA4200D55B50 /* AttachmentUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentUploader.swift; sourceTree = ""; }; FD78E9F52DDD43AB00D55B50 /* Mutation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mutation.swift; sourceTree = ""; }; - FD78E9F72DDD742100D55B50 /* _027_MoveSettingsToLibSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _027_MoveSettingsToLibSession.swift; sourceTree = ""; }; + FD78E9F72DDD742100D55B50 /* _042_MoveSettingsToLibSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _042_MoveSettingsToLibSession.swift; sourceTree = ""; }; FD78E9FC2DDD97F000D55B50 /* Setting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Setting.swift; sourceTree = ""; }; FD78EA012DDEBC2C00D55B50 /* DebounceTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebounceTaskManager.swift; sourceTree = ""; }; FD78EA032DDEC3C000D55B50 /* MultiTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiTaskManager.swift; sourceTree = ""; }; FD78EA052DDEC8F100D55B50 /* AsyncSequence+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AsyncSequence+Utilities.swift"; sourceTree = ""; }; FD78EA092DDFE45900D55B50 /* Interaction+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Interaction+UI.swift"; sourceTree = ""; }; FD78EA0C2DDFEDDF00D55B50 /* LibSession+Local.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LibSession+Local.swift"; sourceTree = ""; }; - FD7F74562BAA9D31006DDFD8 /* _006_DropSnodeCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _006_DropSnodeCache.swift; sourceTree = ""; }; + FD7F74562BAA9D31006DDFD8 /* _022_DropSnodeCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _022_DropSnodeCache.swift; sourceTree = ""; }; FD7F745A2BAAA35E006DDFD8 /* LibSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSession.swift; sourceTree = ""; }; FD7F745E2BAAA3B4006DDFD8 /* TypeConversion+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TypeConversion+Utilities.swift"; sourceTree = ""; }; FD7F74692BAB8A6D006DDFD8 /* LibSession+Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LibSession+Networking.swift"; sourceTree = ""; }; @@ -2128,7 +2143,7 @@ FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionIdSpec.swift; sourceTree = ""; }; FD83B9BD27CF2243005E1583 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSpec.swift; sourceTree = ""; }; - FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitiesSpec.swift; sourceTree = ""; }; + FD83B9C627CF3F10005E1583 /* CapabilitySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitySpec.swift; sourceTree = ""; }; FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageResponse.swift; sourceTree = ""; }; FD83B9D127D59495005E1583 /* MockUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefaults.swift; sourceTree = ""; }; FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryWithDependencies.swift; sourceTree = ""; }; @@ -2146,7 +2161,7 @@ FD860CB52D66913B00BBE29C /* ThemePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreviewView.swift; sourceTree = ""; }; FD860CB72D66BC9500BBE29C /* AppIconViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconViewModel.swift; sourceTree = ""; }; FD860CB92D66BF2300BBE29C /* AppIconGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconGridView.swift; sourceTree = ""; }; - FD860CBB2D6E7A9400BBE29C /* _024_FixBustedInteractionVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _024_FixBustedInteractionVariant.swift; sourceTree = ""; }; + FD860CBB2D6E7A9400BBE29C /* _038_FixBustedInteractionVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _038_FixBustedInteractionVariant.swift; sourceTree = ""; }; FD860CBD2D6E7DA000BBE29C /* DeveloperSettingsViewModel+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeveloperSettingsViewModel+Testing.swift"; sourceTree = ""; }; FD86FDA22BC5020600EC251B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; FD87DCF928B74DB300AF0F98 /* ConversationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSettingsViewModel.swift; sourceTree = ""; }; @@ -2158,12 +2173,12 @@ FD8A5B242DC05B16004C689B /* Number+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Number+Utilities.swift"; sourceTree = ""; }; FD8A5B282DC060DD004C689B /* Double+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Utilities.swift"; sourceTree = ""; }; FD8A5B2F2DC18D5E004C689B /* GeneralCacheSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralCacheSpec.swift; sourceTree = ""; }; - FD8A5B312DC191AB004C689B /* _025_DropLegacyClosedGroupKeyPairTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _025_DropLegacyClosedGroupKeyPairTable.swift; sourceTree = ""; }; - FD8A5B332DC1A726004C689B /* _008_ResetUserConfigLastHashes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _008_ResetUserConfigLastHashes.swift; sourceTree = ""; }; + FD8A5B312DC191AB004C689B /* _039_DropLegacyClosedGroupKeyPairTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _039_DropLegacyClosedGroupKeyPairTable.swift; sourceTree = ""; }; + FD8A5B332DC1A726004C689B /* _024_ResetUserConfigLastHashes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _024_ResetUserConfigLastHashes.swift; sourceTree = ""; }; FD8ECF7A29340FFD00C0D1BB /* LibSession+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LibSession+SessionMessagingKit.swift"; sourceTree = ""; }; - FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_SessionUtilChanges.swift; sourceTree = ""; }; + FD8ECF7C2934293A00C0D1BB /* _027_SessionUtilChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _027_SessionUtilChanges.swift; sourceTree = ""; }; FD8ECF7E2934298100C0D1BB /* ConfigDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigDump.swift; sourceTree = ""; }; - FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; + FD9004132818AD0B00ABAAF6 /* _002_SUK_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SUK_SetupStandardJobs.swift; sourceTree = ""; }; FD9401CE2ABD04AC003A4834 /* TRANSLATIONS.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = TRANSLATIONS.md; sourceTree = ""; }; FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSendJobSpec.swift; sourceTree = ""; }; FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARC4RandomNumberGenerator.swift; sourceTree = ""; }; @@ -2206,7 +2221,7 @@ FDB3DA8C2E24881200148F8D /* ImageLoading+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImageLoading+Convenience.swift"; sourceTree = ""; }; FDB4BBC62838B91E00B7C95D /* LinkPreviewError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewError.swift; sourceTree = ""; }; FDB5DAC02A9443A5002C8721 /* MessageSender+Groups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageSender+Groups.swift"; sourceTree = ""; }; - FDB5DAC62A9447E7002C8721 /* _022_GroupsRebuildChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _022_GroupsRebuildChanges.swift; sourceTree = ""; }; + FDB5DAC62A9447E7002C8721 /* _036_GroupsRebuildChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _036_GroupsRebuildChanges.swift; sourceTree = ""; }; FDB5DAD32A9483F3002C8721 /* GroupUpdateInviteMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupUpdateInviteMessage.swift; sourceTree = ""; }; FDB5DAD92A95D839002C8721 /* GroupUpdateInfoChangeMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupUpdateInfoChangeMessage.swift; sourceTree = ""; }; FDB5DADB2A95D840002C8721 /* GroupUpdateMemberChangeMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupUpdateMemberChangeMessage.swift; sourceTree = ""; }; @@ -2216,13 +2231,13 @@ FDB5DAE52A95D8B0002C8721 /* GroupUpdateDeleteMemberContentMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupUpdateDeleteMemberContentMessage.swift; sourceTree = ""; }; FDB5DAE72A95D96C002C8721 /* MessageReceiver+Groups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+Groups.swift"; sourceTree = ""; }; FDB5DAF22A96DD4F002C8721 /* PreparedRequest+Sending.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PreparedRequest+Sending.swift"; sourceTree = ""; }; - FDB5DAFA2A981C42002C8721 /* SessionSnodeKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionSnodeKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FDB5DAFA2A981C42002C8721 /* SessionNetworkingKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionNetworkingKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; FDB5DB052A981C67002C8721 /* PreparedRequestSendingSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreparedRequestSendingSpec.swift; sourceTree = ""; }; FDB6A87B2AD75B7F002D4F96 /* PhotosUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PhotosUI.framework; path = System/Library/Frameworks/PhotosUI.framework; sourceTree = SDKROOT; }; FDB7400A28EB99A70094D718 /* TimeInterval+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Utilities.swift"; sourceTree = ""; }; FDB7400C28EBEC240094D718 /* DateHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateHeaderCell.swift; sourceTree = ""; }; FDBA8A832D59796F007C19C0 /* FailedGroupInvitesAndPromotionsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedGroupInvitesAndPromotionsJob.swift; sourceTree = ""; }; - FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_AddJobPriority.swift; sourceTree = ""; }; + FDBB25E22988B13800F1508E /* _012_AddJobPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _012_AddJobPriority.swift; sourceTree = ""; }; FDBB25E62988BBBD00F1508E /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = ""; }; FDBEE52D2B6A18B900C143A0 /* UserDefaultsConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsConfig.swift; sourceTree = ""; }; FDC0F0032BFECE12002CBFB9 /* TimeUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeUnit.swift; sourceTree = ""; }; @@ -2230,18 +2245,16 @@ FDC13D462A16E4CA007267C7 /* SubscribeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribeRequest.swift; sourceTree = ""; }; FDC13D482A16EC20007267C7 /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; }; FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribeResponse.swift; sourceTree = ""; }; - FDC13D4F2A16EE50007267C7 /* PushNotificationAPIEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationAPIEndpoint.swift; sourceTree = ""; }; - FDC13D532A16FF29007267C7 /* LegacyGroupRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGroupRequest.swift; sourceTree = ""; }; + FDC13D4F2A16EE50007267C7 /* PushNotificationEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationEndpoint.swift; sourceTree = ""; }; FDC13D552A171FE4007267C7 /* UnsubscribeRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsubscribeRequest.swift; sourceTree = ""; }; FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsubscribeResponse.swift; sourceTree = ""; }; - FDC13D592A1721C5007267C7 /* LegacyNotifyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyNotifyRequest.swift; sourceTree = ""; }; FDC1BD652CFD6C4E002CDC71 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; FDC1BD672CFE6EEA002CDC71 /* DeveloperSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsViewModel.swift; sourceTree = ""; }; FDC1BD692CFE7B67002CDC71 /* DirectoryArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryArchiver.swift; sourceTree = ""; }; FDC289462C881A3800020BC2 /* MutableIdentifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutableIdentifiable.swift; sourceTree = ""; }; FDC2908627D7047F005DAE71 /* RoomSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSpec.swift; sourceTree = ""; }; FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollInfoSpec.swift; sourceTree = ""; }; - FDC2908A27D707F3005DAE71 /* SendMessageRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequestSpec.swift; sourceTree = ""; }; + FDC2908A27D707F3005DAE71 /* SendSOGSMessageRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendSOGSMessageRequestSpec.swift; sourceTree = ""; }; FDC2908C27D70905005DAE71 /* UpdateMessageRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequestSpec.swift; sourceTree = ""; }; FDC2908E27D70938005DAE71 /* SendDirectMessageRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageRequestSpec.swift; sourceTree = ""; }; FDC2909027D709CA005DAE71 /* SOGSMessageSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSMessageSpec.swift; sourceTree = ""; }; @@ -2250,20 +2263,19 @@ FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalizationSpec.swift; sourceTree = ""; }; FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManagerSpec.swift; sourceTree = ""; }; FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbleExtensions.swift; sourceTree = ""; }; - FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPIError.swift; sourceTree = ""; }; + FDC4380827B31D4E00C60D73 /* SOGSError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSError.swift; sourceTree = ""; }; FDC4381627B32EC700C60D73 /* Personalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Personalization.swift; sourceTree = ""; }; FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSEndpoint.swift; sourceTree = ""; }; - FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyPushServerResponse.swift; sourceTree = ""; }; FDC4383727B3863200C60D73 /* AppVersionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionResponse.swift; sourceTree = ""; }; FDC4385C27B4C18900C60D73 /* Room.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = ""; }; FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedMessage.swift; sourceTree = ""; }; FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSMessage.swift; sourceTree = ""; }; FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollInfo.swift; sourceTree = ""; }; - FDC4386627B4E10E00C60D73 /* Capabilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Capabilities.swift; sourceTree = ""; }; + FDC4386627B4E10E00C60D73 /* CapabilitiesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapabilitiesResponse.swift; sourceTree = ""; }; FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponse.swift; sourceTree = ""; }; - FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageRequest.swift; sourceTree = ""; }; + FDC4387727B5C35400C60D73 /* SendSOGSMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendSOGSMessageRequest.swift; sourceTree = ""; }; FDC4388E27B9FFC700C60D73 /* SessionMessagingKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionMessagingKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPISpec.swift; sourceTree = ""; }; + FDC4389927BA002500C60D73 /* SOGSAPISpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSAPISpec.swift; sourceTree = ""; }; FDC438A327BB107F00C60D73 /* UserBanRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBanRequest.swift; sourceTree = ""; }; FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserUnbanRequest.swift; sourceTree = ""; }; FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserModeratorRequest.swift; sourceTree = ""; }; @@ -2274,7 +2286,6 @@ FDC498B82AC15FE300EDD897 /* AppNotificationAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNotificationAction.swift; sourceTree = ""; }; FDC6D75F2862B3F600B04575 /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = ""; }; FDCCC6E82ABA7402002BBEF5 /* EmojiGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiGenerator.swift; sourceTree = ""; }; - FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyGroupOnlyRequest.swift; sourceTree = ""; }; FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; FDD20C152A09E64A003898FB /* GetExpiriesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetExpiriesRequest.swift; sourceTree = ""; }; FDD20C172A09E7D3003898FB /* GetExpiriesResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetExpiriesResponse.swift; sourceTree = ""; }; @@ -2284,15 +2295,14 @@ FDD383702AFDD0E1001367F2 /* BencodeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BencodeResponse.swift; sourceTree = ""; }; FDD383722AFDD6D7001367F2 /* BencodeResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BencodeResponseSpec.swift; sourceTree = ""; }; FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessResult.swift; sourceTree = ""; }; - FDDD554D2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _019_ScheduleAppUpdateCheckJob.swift; sourceTree = ""; }; + FDDD554D2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _033_ScheduleAppUpdateCheckJob.swift; sourceTree = ""; }; FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequest+Utilities.swift"; sourceTree = ""; }; FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerSpec.swift; sourceTree = ""; }; FDE125222A837E4E002DA685 /* MainAppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainAppContext.swift; sourceTree = ""; }; FDE33BBB2D5C124300E56F42 /* DispatchTimeInterval+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchTimeInterval+Utilities.swift"; sourceTree = ""; }; - FDE33BBD2D5C3AE800E56F42 /* _023_GroupsExpiredFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _023_GroupsExpiredFlag.swift; sourceTree = ""; }; + FDE33BBD2D5C3AE800E56F42 /* _037_GroupsExpiredFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _037_GroupsExpiredFlag.swift; sourceTree = ""; }; FDE519F62AB7CDC700450C53 /* Result+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Utilities.swift"; sourceTree = ""; }; FDE5218D2E03A06700061B8E /* AttachmentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentManager.swift; sourceTree = ""; }; - FDE5218F2E04CCE600061B8E /* AVURLAsset+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVURLAsset+Utilities.swift"; sourceTree = ""; }; FDE521932E050B0800061B8E /* DismissCallbackAVPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissCallbackAVPlayerViewController.swift; sourceTree = ""; }; FDE521992E08DBB000061B8E /* ImageLoading+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImageLoading+Convenience.swift"; sourceTree = ""; }; FDE5219B2E08E76600061B8E /* SessionAsyncImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionAsyncImage.swift; sourceTree = ""; }; @@ -2300,11 +2310,16 @@ FDE5219F2E0D22FD00061B8E /* ObservationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservationManager.swift; sourceTree = ""; }; FDE521A12E0D23A200061B8E /* ObservableKey+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObservableKey+SessionMessagingKit.swift"; sourceTree = ""; }; FDE6E99729F8E63A00F93C5D /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = ""; }; + FDE71B042E77E1A00023F5F9 /* ObservableKey+SessionNetworkingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ObservableKey+SessionNetworkingKit.swift"; sourceTree = ""; }; + FDE71B0A2E7935250023F5F9 /* DeveloperSettingsGroupsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsGroupsViewModel.swift; sourceTree = ""; }; + FDE71B0C2E793B1F0023F5F9 /* DeveloperSettingsProViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsProViewModel.swift; sourceTree = ""; }; + FDE71B0E2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Session - Anonymous Messenger.storekit"; sourceTree = ""; }; + FDE71B5E2E7A73560023F5F9 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = ""; }; FDE7549A2C940108002A2623 /* MessageViewModel+DeletionActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageViewModel+DeletionActions.swift"; sourceTree = ""; }; FDE7549C2C9961A4002A2623 /* CommunityPoller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommunityPoller.swift; sourceTree = ""; }; - FDE754A02C9A60A6002A2623 /* Crypto+OpenGroupAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+OpenGroupAPI.swift"; sourceTree = ""; }; + FDE754A02C9A60A6002A2623 /* Crypto+OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+OpenGroup.swift"; sourceTree = ""; }; FDE754A22C9A8FD1002A2623 /* SwarmPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwarmPoller.swift; sourceTree = ""; }; FDE754A42C9B964D002A2623 /* MessageReceiverGroupsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageReceiverGroupsSpec.swift; sourceTree = ""; }; FDE754A52C9B964D002A2623 /* MessageSenderGroupsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageSenderGroupsSpec.swift; sourceTree = ""; }; @@ -2332,7 +2347,7 @@ FDE754D82C9BAF89002A2623 /* Crypto+SessionUtilitiesKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+SessionUtilitiesKit.swift"; sourceTree = ""; }; FDE754D92C9BAF89002A2623 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = ""; }; FDE754DA2C9BAF8A002A2623 /* Hex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Hex.swift; sourceTree = ""; }; - FDE754E12C9BAFF4002A2623 /* Crypto+SessionSnodeKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+SessionSnodeKit.swift"; sourceTree = ""; }; + FDE754E12C9BAFF4002A2623 /* Crypto+SessionNetworkingKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+SessionNetworkingKit.swift"; sourceTree = ""; }; FDE754E42C9BB012002A2623 /* BezierPathView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BezierPathView.swift; sourceTree = ""; }; FDE754E62C9BB051002A2623 /* OWSViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSViewController.swift; sourceTree = ""; }; FDE754EC2C9BB08B002A2623 /* Crypto+Attachments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Crypto+Attachments.swift"; sourceTree = ""; }; @@ -2341,9 +2356,9 @@ FDE754F32C9BB0AF002A2623 /* UserNotificationConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotificationConfig.swift; sourceTree = ""; }; FDE754F42C9BB0AF002A2623 /* NotificationActionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationActionHandler.swift; sourceTree = ""; }; FDE754F52C9BB0AF002A2623 /* NotificationPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationPresenter.swift; sourceTree = ""; }; - FDE754FD2C9BB0D0002A2623 /* Threading+SMK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Threading+SMK.swift"; sourceTree = ""; }; + FDE754FD2C9BB0D0002A2623 /* Threading+SessionMessagingKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Threading+SessionMessagingKit.swift"; sourceTree = ""; }; FDE754FF2C9BB0FA002A2623 /* SessionEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionEnvironment.swift; sourceTree = ""; }; - FDE755012C9BB122002A2623 /* _011_AddPendingReadReceipts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _011_AddPendingReadReceipts.swift; sourceTree = ""; }; + FDE755012C9BB122002A2623 /* _025_AddPendingReadReceipts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _025_AddPendingReadReceipts.swift; sourceTree = ""; }; FDE755032C9BB4ED002A2623 /* BencodeDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BencodeDecoder.swift; sourceTree = ""; }; FDE755042C9BB4ED002A2623 /* Bencode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bencode.swift; sourceTree = ""; }; FDE755132C9BC169002A2623 /* UIAlertAction+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertAction+Utilities.swift"; sourceTree = ""; }; @@ -2370,7 +2385,7 @@ FDF01FAC2A9ECC4200CAF969 /* SingletonConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingletonConfig.swift; sourceTree = ""; }; FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreview.swift; sourceTree = ""; }; FDF0B73F280402C4004C14C5 /* Job.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Job.swift; sourceTree = ""; }; - FDF0B7412804EA4F004C14C5 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; + FDF0B7412804EA4F004C14C5 /* _007_SMK_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _007_SMK_SetupStandardJobs.swift; sourceTree = ""; }; FDF0B7432804EF1B004C14C5 /* JobRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunner.swift; sourceTree = ""; }; FDF0B74828060D13004C14C5 /* QuotedReplyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuotedReplyModel.swift; sourceTree = ""; }; FDF0B74A28061F7A004C14C5 /* InteractionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionAttachment.swift; sourceTree = ""; }; @@ -2385,13 +2400,12 @@ FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueryInterfaceRequest+Utilities.swift"; sourceTree = ""; }; FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TableRecord+Utilities.swift"; sourceTree = ""; }; FDF2F0212DAE1AEF00491E8A /* MessageReceiver+LegacyClosedGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+LegacyClosedGroups.swift"; sourceTree = ""; }; - FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _004_RemoveLegacyYDB.swift; sourceTree = ""; }; + FDF40CDD2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _011_RemoveLegacyYDB.swift; sourceTree = ""; }; FDF71EA22B072C2800A8D6B5 /* LibSessionMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionMessage.swift; sourceTree = ""; }; FDF71EA42B07363500A8D6B5 /* MessageReceiver+LibSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+LibSession.swift"; sourceTree = ""; }; - FDF8487D29405993007DCAE5 /* HTTPHeader+OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPHeader+OpenGroup.swift"; sourceTree = ""; }; - FDF8487E29405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPQueryParam+OpenGroup.swift"; sourceTree = ""; }; + FDF8487D29405993007DCAE5 /* HTTPHeader+SOGS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPHeader+SOGS.swift"; sourceTree = ""; }; + FDF8487E29405994007DCAE5 /* HTTPQueryParam+SOGS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPQueryParam+SOGS.swift"; sourceTree = ""; }; FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPINamespace.swift; sourceTree = ""; }; - FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPI.swift; sourceTree = ""; }; FDF8489A29405C5A007DCAE5 /* SnodeRecursiveResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeRecursiveResponse.swift; sourceTree = ""; }; FDF8489B29405C5A007DCAE5 /* GetMessagesRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetMessagesRequest.swift; sourceTree = ""; }; FDF8489D29405C5A007DCAE5 /* SnodeResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeResponse.swift; sourceTree = ""; }; @@ -2434,7 +2448,7 @@ FDFDE125282D05380098B17F /* MediaInteractiveDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInteractiveDismiss.swift; sourceTree = ""; }; FDFDE127282D05530098B17F /* MediaPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPresentationContext.swift; sourceTree = ""; }; FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaZoomAnimationController.swift; sourceTree = ""; }; - FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _016_MakeBrokenProfileTimestampsNullable.swift; sourceTree = ""; }; + FDFE75B02ABD2D2400655640 /* _030_MakeBrokenProfileTimestampsNullable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _030_MakeBrokenProfileTimestampsNullable.swift; sourceTree = ""; }; FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONEncoder+Utilities.swift"; sourceTree = ""; }; FED288F22E4C28CF00C31171 /* AppReviewPromptDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReviewPromptDialog.swift; sourceTree = ""; }; FED288F72E4C3BE100C31171 /* AppReviewPromptModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReviewPromptModel.swift; sourceTree = ""; }; @@ -2446,7 +2460,7 @@ buildActionMask = 2147483647; files = ( FD860CC92D6ED2ED00BBE29C /* DifferenceKit in Frameworks */, - C32824D325C9F9790062D0A7 /* SessionSnodeKit.framework in Frameworks */, + C32824D325C9F9790062D0A7 /* SessionNetworkingKit.framework in Frameworks */, B8D64FC725BA78520029CFC0 /* SessionMessagingKit.framework in Frameworks */, C3D90A5C25773A25002C9DF5 /* SessionUtilitiesKit.framework in Frameworks */, C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */, @@ -2460,7 +2474,7 @@ files = ( B8D64FBB25BA78310029CFC0 /* SessionMessagingKit.framework in Frameworks */, FD8A5B182DBF47E9004C689B /* SessionUIKit.framework in Frameworks */, - B8D64FBD25BA78310029CFC0 /* SessionSnodeKit.framework in Frameworks */, + B8D64FBD25BA78310029CFC0 /* SessionNetworkingKit.framework in Frameworks */, B8D64FBE25BA78310029CFC0 /* SessionUtilitiesKit.framework in Frameworks */, C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */, ); @@ -2483,7 +2497,7 @@ FD6A39222C2AA91D00762359 /* NVActivityIndicatorView in Frameworks */, FD22866F2C38D42300BC06F7 /* DifferenceKit in Frameworks */, C33FD9C2255A54EF00E217F9 /* SessionMessagingKit.framework in Frameworks */, - C33FD9C4255A54EF00E217F9 /* SessionSnodeKit.framework in Frameworks */, + C33FD9C4255A54EF00E217F9 /* SessionNetworkingKit.framework in Frameworks */, C33FD9C5255A54EF00E217F9 /* SessionUtilitiesKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2518,7 +2532,7 @@ FD6673FA2D7021F800041530 /* SessionUtil in Frameworks */, FD2286732C38D43900BC06F7 /* DifferenceKit in Frameworks */, FDC4386C27B4E90300C60D73 /* SessionUtilitiesKit.framework in Frameworks */, - C3C2A70B25539E1E00C340D1 /* SessionSnodeKit.framework in Frameworks */, + C3C2A70B25539E1E00C340D1 /* SessionNetworkingKit.framework in Frameworks */, FD6A39132C2A946A00762359 /* SwiftProtobuf in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2532,7 +2546,7 @@ B8FF8DAE25C0D00F004D1F22 /* SessionMessagingKit.framework in Frameworks */, B8FF8DAF25C0D00F004D1F22 /* SessionUtilitiesKit.framework in Frameworks */, FDB6A87C2AD75B7F002D4F96 /* PhotosUI.framework in Frameworks */, - C37F54DC255BB84A002AEA92 /* SessionSnodeKit.framework in Frameworks */, + C37F54DC255BB84A002AEA92 /* SessionNetworkingKit.framework in Frameworks */, C37F5414255BAFA7002AEA92 /* SignalUtilitiesKit.framework in Frameworks */, 455A16DD1F1FEA0000F86704 /* Metal.framework in Frameworks */, 455A16DE1F1FEA0000F86704 /* MetalKit.framework in Frameworks */, @@ -2554,6 +2568,7 @@ A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */, FD6DA9CF2D015B440092085A /* Lucide in Frameworks */, A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */, + FDE71B5F2E7A73570023F5F9 /* StoreKit.framework in Frameworks */, D2AEACDC16C426DA00C364C0 /* CFNetwork.framework in Frameworks */, C331FF222558F9D300070591 /* SessionUIKit.framework in Frameworks */, D2179CFE16BB0B480006F3AB /* SystemConfiguration.framework in Frameworks */, @@ -2571,7 +2586,7 @@ files = ( FD0150542CA24471005B08A1 /* Nimble in Frameworks */, FD0150522CA2446D005B08A1 /* Quick in Frameworks */, - FD0150502CA24468005B08A1 /* SessionSnodeKit.framework in Frameworks */, + FD0150502CA24468005B08A1 /* SessionNetworkingKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2859,16 +2874,16 @@ path = SwiftUI; sourceTree = ""; }; - 947D7FD32D509FC900E8E413 /* SessionNetworkAPI */ = { + 947D7FD32D509FC900E8E413 /* SessionNetwork */ = { isa = PBXGroup; children = ( - 941375BA2D5184B60058F244 /* HTTPHeader+SessionNetwork.swift */, + FD6B92952E77A036004463B5 /* Models */, + FD6B929E2E77A0E8004463B5 /* Types */, + FD6B92912E779FC6004463B5 /* SessionNetwork.swift */, 947D7FCF2D509FC900E8E413 /* SessionNetworkAPI.swift */, - 947D7FD02D509FC900E8E413 /* SessionNetworkAPI+Database.swift */, - 947D7FD12D509FC900E8E413 /* SessionNetworkAPI+Models.swift */, - 947D7FD22D509FC900E8E413 /* SessionNetworkAPI+Network.swift */, + FD6B92932E779FFE004463B5 /* SessionNetworkEndpoint.swift */, ); - path = SessionNetworkAPI; + path = SessionNetwork; sourceTree = ""; }; 947D7FDC2D5180F200E8E413 /* SessionNetworkScreen */ = { @@ -2993,7 +3008,6 @@ B8A582AB258C64E800AFD84C /* Database */ = { isa = PBXGroup; children = ( - FD17D7C827F546CE00122BE0 /* Migrations */, FD17D7CB27F546F500122BE0 /* Models */, FD17D7B427F51E6700122BE0 /* Types */, FD17D7BB27F51F5C00122BE0 /* Utilities */, @@ -3451,6 +3465,7 @@ C360969125AD1765008B62B2 /* Settings */ = { isa = PBXGroup; children = ( + FDE71B092E7934DC0023F5F9 /* DeveloperSettings */, FD8A5B002DBEFBF9004C689B /* SessionNetworkScreen */, FD37E9CD28A1E682003AE748 /* Views */, 9422569A2C23F8F000C0FDBF /* QRCodeScreen.swift */, @@ -3466,8 +3481,6 @@ FD860CB72D66BC9500BBE29C /* AppIconViewModel.swift */, FD37EA0228A9FDCC003AE748 /* HelpViewModel.swift */, B86BD08523399CEF000F5AE3 /* SeedModal.swift */, - FDC1BD672CFE6EEA002CDC71 /* DeveloperSettingsViewModel.swift */, - FD860CBD2D6E7DA000BBE29C /* DeveloperSettingsViewModel+Testing.swift */, B894D0742339EDCF00B4D94D /* NukeDataModal.swift */, FDF848F229413DB0007DCAE5 /* ImagePickerHandler.swift */, ); @@ -3575,9 +3588,8 @@ isa = PBXGroup; children = ( FDC13D4E2A16EE41007267C7 /* Types */, - FDC4382D27B383A600C60D73 /* Models */, FDF0B7502807BA56004C14C5 /* NotificationsManagerType.swift */, - C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */, + FD6B92E32E77C250004463B5 /* PushNotificationAPI+SessionMessagingKit.swift */, ); path = Notifications; sourceTree = ""; @@ -3633,9 +3645,7 @@ isa = PBXGroup; children = ( FD23CE202A661CE80000B97C /* Crypto */, - FDC4381827B34EAD00C60D73 /* Models */, - FDC4380727B31D3A00C60D73 /* Types */, - B88FA7B726045D100049422F /* OpenGroupAPI.swift */, + FDC4381827B34EAD00C60D73 /* Types */, C3DB66AB260ACA42001EFC55 /* OpenGroupManager.swift */, ); path = "Open Groups"; @@ -3672,33 +3682,34 @@ C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */, FDE754FF2C9BB0FA002A2623 /* SessionEnvironment.swift */, C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */, - FDE754FD2C9BB0D0002A2623 /* Threading+SMK.swift */, + FDE754FD2C9BB0D0002A2623 /* Threading+SessionMessagingKit.swift */, FD72BDA02BE368C800CF6CF6 /* UIWindowLevel+Utilities.swift */, ); path = Utilities; sourceTree = ""; }; - C3C2A5A0255385C100C340D1 /* SessionSnodeKit */ = { + C3C2A5A0255385C100C340D1 /* SessionNetworkingKit */ = { isa = PBXGroup; children = ( - 947D7FD32D509FC900E8E413 /* SessionNetworkAPI */, C3C2A5B0255385C700C340D1 /* Meta */, FDE754E22C9BAFF4002A2623 /* Crypto */, - FD17D79D27F40CAA00122BE0 /* Database */, - FDF8489929405C5A007DCAE5 /* Models */, - FDF8488F29405C13007DCAE5 /* Types */, - FD2272842C33E28D004D8A6C /* SnodeAPI */, + FD6B928A2E779DB6004463B5 /* FileServer */, FD7F74682BAB8A5D006DDFD8 /* LibSession */, + FD6B92DF2E77C1CB004463B5 /* PushNotification */, + 947D7FD32D509FC900E8E413 /* SessionNetwork */, + FD6B92892E779D8D004463B5 /* SOGS */, + FD2272842C33E28D004D8A6C /* StorageServer */, + FD6B92A52E77A3BD004463B5 /* Models */, + FDF8488F29405C13007DCAE5 /* Types */, C3C2A5CD255385F300C340D1 /* Utilities */, - C3C2A5B9255385ED00C340D1 /* Configuration.swift */, ); - path = SessionSnodeKit; + path = SessionNetworkingKit; sourceTree = ""; }; C3C2A5B0255385C700C340D1 /* Meta */ = { isa = PBXGroup; children = ( - C3C2A5A1255385C100C340D1 /* SessionSnodeKit.h */, + C3C2A5A1255385C100C340D1 /* SessionNetworkingKit.h */, C3C2A5A2255385C100C340D1 /* Info.plist */, ); path = Meta; @@ -3708,10 +3719,10 @@ isa = PBXGroup; children = ( C3C2A5D82553860B00C340D1 /* Data+Utilities.swift */, + FDE71B042E77E1A00023F5F9 /* ObservableKey+SessionNetworkingKit.swift */, FD2272BD2C34B710004D8A6C /* Publisher+Utilities.swift */, FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */, C3C2A5D22553860900C340D1 /* String+Trimming.swift */, - C3C2A5D42553860A00C340D1 /* Threading+SSK.swift */, FD2272A82C33E337004D8A6C /* URLResponse+Utilities.swift */, ); path = Utilities; @@ -3848,6 +3859,7 @@ FDE125222A837E4E002DA685 /* MainAppContext.swift */, C3CA3AA0255CDA7000F4C6D4 /* Mnemonic */, FD86FDA22BC5020600EC251B /* PrivacyInfo.xcprivacy */, + FDE71B0E2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit */, B67EBF5C19194AC60084CCFD /* Settings.bundle */, B657DDC91911A40500F45B0C /* Signal.entitlements */, FDF2220A2818F38D000A4995 /* SessionApp.swift */, @@ -3868,18 +3880,17 @@ 7BC01A3C241F40AB00BC7C55 /* SessionNotificationServiceExtension */, C331FF1C2558F9D300070591 /* SessionUIKit */, C3C2A6F125539DE700C340D1 /* SessionMessagingKit */, - C3C2A5A0255385C100C340D1 /* SessionSnodeKit */, + C3C2A5A0255385C100C340D1 /* SessionNetworkingKit */, C3C2A67A255388CC00C340D1 /* SessionUtilitiesKit */, C33FD9AC255A548A00E217F9 /* SignalUtilitiesKit */, FD83B9BC27CF2215005E1583 /* _SharedTestUtilities */, FD71160A28D00BAE00B47552 /* SessionTests */, FDC4388F27B9FFC700C60D73 /* SessionMessagingKitTests */, - FDB5DAFB2A981C43002C8721 /* SessionSnodeKitTests */, + FDB5DAFB2A981C43002C8721 /* SessionNetworkingKitTests */, FD83B9B027CF200A005E1583 /* SessionUtilitiesKitTests */, FDE7214E287E50D50093DF33 /* Scripts */, D221A08C169C9E5E00537ABF /* Frameworks */, D221A08A169C9E5E00537ABF /* Products */, - FD8A5B232DC05A0E004C689B /* Recovered References */, ); sourceTree = ""; }; @@ -3889,7 +3900,7 @@ D221A089169C9E5E00537ABF /* Session.app */, 453518681FC635DD00210559 /* SessionShareExtension.appex */, 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */, - C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */, + C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */, C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */, C3C2A6F025539DE700C340D1 /* SessionMessagingKit.framework */, C331FF1B2558F9D300070591 /* SessionUIKit.framework */, @@ -3897,7 +3908,7 @@ FDC4388E27B9FFC700C60D73 /* SessionMessagingKitTests.xctest */, FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */, FD71160928D00BAE00B47552 /* SessionTests.xctest */, - FDB5DAFA2A981C42002C8721 /* SessionSnodeKitTests.xctest */, + FDB5DAFA2A981C42002C8721 /* SessionNetworkingKitTests.xctest */, ); name = Products; sourceTree = ""; @@ -3905,6 +3916,7 @@ D221A08C169C9E5E00537ABF /* Frameworks */ = { isa = PBXGroup; children = ( + FDE71B5E2E7A73560023F5F9 /* StoreKit.framework */, FDB6A87B2AD75B7F002D4F96 /* PhotosUI.framework */, 3496955F21A2FC8100DCFE74 /* CloudKit.framework */, 455A16DB1F1FEA0000F86704 /* Metal.framework */, @@ -3964,7 +3976,6 @@ isa = PBXGroup; children = ( FDB3DA892E2482A400148F8D /* AVURLAsset+Utilities.swift */, - FDE5218F2E04CCE600061B8E /* AVURLAsset+Utilities.swift */, 94C58AC82D2E036E00609195 /* Permissions.swift */, FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */, FD78EA052DDEC8F100D55B50 /* AsyncSequence+Utilities.swift */, @@ -4028,69 +4039,60 @@ FD17D79427F3E03300122BE0 /* Migrations */ = { isa = PBXGroup; children = ( - FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */, - FDF0B7412804EA4F004C14C5 /* _002_SetupStandardJobs.swift */, - FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */, - FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */, - FD37EA0C28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift */, - FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */, - 7B81682728B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift */, - FD09B7E4288670BB00ED0B66 /* _008_EmojiReacts.swift */, - 7BAA7B6528D2DE4700AE1489 /* _009_OpenGroupPermission.swift */, - FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */, - FDE755012C9BB122002A2623 /* _011_AddPendingReadReceipts.swift */, - FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */, - FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */, - FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */, - FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */, - FDFE75B02ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift */, - FD428B222B4B9969006D0888 /* _017_RebuildFTSIfNeeded_2_4_5.swift */, - 7B5233C5290636D700F8F375 /* _018_DisappearingMessagesConfiguration.swift */, - FDDD554D2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift */, - FD3559452CC1FF140088F2A9 /* _020_AddMissingWhisperFlag.swift */, - FD4C53AE2CC1D61E003B10F4 /* _021_ReworkRecipientState.swift */, - FDB5DAC62A9447E7002C8721 /* _022_GroupsRebuildChanges.swift */, - FDE33BBD2D5C3AE800E56F42 /* _023_GroupsExpiredFlag.swift */, - FD860CBB2D6E7A9400BBE29C /* _024_FixBustedInteractionVariant.swift */, - FD8A5B312DC191AB004C689B /* _025_DropLegacyClosedGroupKeyPairTable.swift */, - FD70F25B2DC1F176003729B7 /* _026_MessageDeduplicationTable.swift */, - FD78E9F72DDD742100D55B50 /* _027_MoveSettingsToLibSession.swift */, - FD05594D2E012D1A00DC48CE /* _028_RenameAttachments.swift */, - 94CD95C02E0CBF1C0097754D /* _029_AddProMessageFlag.swift */, + FD17D7C927F546D900122BE0 /* _001_SUK_InitialSetupMigration.swift */, + FD9004132818AD0B00ABAAF6 /* _002_SUK_SetupStandardJobs.swift */, + FD17D7E627F6A16700122BE0 /* _003_SUK_YDBToGRDBMigration.swift */, + FD17D79F27F40CC800122BE0 /* _004_SNK_InitialSetupMigration.swift */, + FD6A7A6C2818C61500035AC1 /* _005_SNK_SetupStandardJobs.swift */, + FD17D79527F3E04600122BE0 /* _006_SMK_InitialSetupMigration.swift */, + FDF0B7412804EA4F004C14C5 /* _007_SMK_SetupStandardJobs.swift */, + FD17D7A327F40F8100122BE0 /* _008_SNK_YDBToGRDBMigration.swift */, + FD17D79827F40AB800122BE0 /* _009_SMK_YDBToGRDBMigration.swift */, + FD39353528F7C3390084DADA /* _010_FlagMessageHashAsDeletedOrInvalid.swift */, + FDF40CDD2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift */, + FDBB25E22988B13800F1508E /* _012_AddJobPriority.swift */, + FD37EA0C28AB2A45003AE748 /* _013_FixDeletedMessageReadState.swift */, + FD37EA0E28AB3330003AE748 /* _014_FixHiddenModAdminSupport.swift */, + 7B81682728B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift */, + FD37E9F828A5F14A003AE748 /* _016_ThemePreferences.swift */, + FD09B7E4288670BB00ED0B66 /* _017_EmojiReacts.swift */, + 7BAA7B6528D2DE4700AE1489 /* _018_OpenGroupPermission.swift */, + FD7115F128C6CB3900B47552 /* _019_AddThreadIdToFTS.swift */, + 945D9C572D6FDBE7003C4C0C /* _020_AddJobUniqueHash.swift */, + FD6DF00A2ACFE40D0084BA4C /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift */, + FD7F74562BAA9D31006DDFD8 /* _022_DropSnodeCache.swift */, + FD61FCFA2D34A5DE005752DE /* _023_SplitSnodeReceivedMessageInfo.swift */, + FD8A5B332DC1A726004C689B /* _024_ResetUserConfigLastHashes.swift */, + FDE755012C9BB122002A2623 /* _025_AddPendingReadReceipts.swift */, + FD368A6729DE8F9B000DBF1E /* _026_AddFTSIfNeeded.swift */, + FD8ECF7C2934293A00C0D1BB /* _027_SessionUtilChanges.swift */, + FD778B6329B189FF001BAC6B /* _028_GenerateInitialUserConfigDumps.swift */, + FD1D732D2A86114600E3F410 /* _029_BlockCommunityMessageRequests.swift */, + FDFE75B02ABD2D2400655640 /* _030_MakeBrokenProfileTimestampsNullable.swift */, + FD428B222B4B9969006D0888 /* _031_RebuildFTSIfNeeded_2_4_5.swift */, + 7B5233C5290636D700F8F375 /* _032_DisappearingMessagesConfiguration.swift */, + FDDD554D2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift */, + FD3559452CC1FF140088F2A9 /* _034_AddMissingWhisperFlag.swift */, + FD4C53AE2CC1D61E003B10F4 /* _035_ReworkRecipientState.swift */, + FDB5DAC62A9447E7002C8721 /* _036_GroupsRebuildChanges.swift */, + FDE33BBD2D5C3AE800E56F42 /* _037_GroupsExpiredFlag.swift */, + FD860CBB2D6E7A9400BBE29C /* _038_FixBustedInteractionVariant.swift */, + FD8A5B312DC191AB004C689B /* _039_DropLegacyClosedGroupKeyPairTable.swift */, + FD70F25B2DC1F176003729B7 /* _040_MessageDeduplicationTable.swift */, + 947D7FE22D5181F400E8E413 /* _041_RenameTableSettingToKeyValueStore.swift */, + FD78E9F72DDD742100D55B50 /* _042_MoveSettingsToLibSession.swift */, + FD05594D2E012D1A00DC48CE /* _043_RenameAttachments.swift */, + 94CD95C02E0CBF1C0097754D /* _044_AddProMessageFlag.swift */, ); path = Migrations; sourceTree = ""; }; FD17D79D27F40CAA00122BE0 /* Database */ = { - isa = PBXGroup; - children = ( - FD17D79E27F40CC000122BE0 /* Migrations */, - FD17D7A827F41BE300122BE0 /* Models */, - ); - path = Database; - sourceTree = ""; - }; - FD17D79E27F40CC000122BE0 /* Migrations */ = { - isa = PBXGroup; - children = ( - FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */, - FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */, - FD17D7A327F40F8100122BE0 /* _003_YDBToGRDBMigration.swift */, - FD39353528F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift */, - FD6DF00A2ACFE40D0084BA4C /* _005_AddSnodeReveivedMessageInfoPrimaryKey.swift */, - FD7F74562BAA9D31006DDFD8 /* _006_DropSnodeCache.swift */, - FD61FCFA2D34A5DE005752DE /* _007_SplitSnodeReceivedMessageInfo.swift */, - FD8A5B332DC1A726004C689B /* _008_ResetUserConfigLastHashes.swift */, - ); - path = Migrations; - sourceTree = ""; - }; - FD17D7A827F41BE300122BE0 /* Models */ = { isa = PBXGroup; children = ( FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */, ); - path = Models; + path = Database; sourceTree = ""; }; FD17D7B427F51E6700122BE0 /* Types */ = { @@ -4099,7 +4101,6 @@ FD17D7BE27F51F8200122BE0 /* ColumnExpressible.swift */, FD17D7B727F51ECA00122BE0 /* Migration.swift */, FD4BB22A2D63F20600D0DC3D /* MigrationHelper.swift */, - FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */, FD7162DA281B6C440060647B /* TypedTableAlias.swift */, FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */, FD1A553D2E14BE0E003761E4 /* PagedData.swift */, @@ -4110,7 +4111,6 @@ FD17D7BB27F51F5C00122BE0 /* Utilities */ = { isa = PBXGroup; children = ( - FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */, FDF22210281B5E0B000A4995 /* TableRecord+Utilities.swift */, FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */, FDF2220E281B55E6000A4995 /* QueryInterfaceRequest+Utilities.swift */, @@ -4121,19 +4121,6 @@ path = Utilities; sourceTree = ""; }; - FD17D7C827F546CE00122BE0 /* Migrations */ = { - isa = PBXGroup; - children = ( - FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */, - FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */, - FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */, - FDBB25E22988B13800F1508E /* _004_AddJobPriority.swift */, - 945D9C572D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift */, - 947D7FE22D5181F400E8E413 /* _006_RenameTableSettingToKeyValueStore.swift */, - ); - path = Migrations; - sourceTree = ""; - }; FD17D7CB27F546F500122BE0 /* Models */ = { isa = PBXGroup; children = ( @@ -4153,17 +4140,18 @@ path = Database; sourceTree = ""; }; - FD2272842C33E28D004D8A6C /* SnodeAPI */ = { + FD2272842C33E28D004D8A6C /* StorageServer */ = { isa = PBXGroup; children = ( - FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */, + FD17D79D27F40CAA00122BE0 /* Database */, + FDF8489929405C5A007DCAE5 /* Models */, + FD6B92A12E77A153004463B5 /* Types */, + FD6B92A22E77A189004463B5 /* SnodeAPI.swift */, FD2272D72C34EDE6004D8A6C /* SnodeAPIEndpoint.swift */, FDF848E029405D6E007DCAE5 /* SnodeAPIError.swift */, FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */, - FD47E0B42AA6D7AA00A55E41 /* Request+SnodeAPI.swift */, - FD19363B2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift */, ); - path = SnodeAPI; + path = StorageServer; sourceTree = ""; }; FD2272C52C34E9D1004D8A6C /* Types */ = { @@ -4205,7 +4193,7 @@ FD23CE202A661CE80000B97C /* Crypto */ = { isa = PBXGroup; children = ( - FDE754A02C9A60A6002A2623 /* Crypto+OpenGroupAPI.swift */, + FDE754A02C9A60A6002A2623 /* Crypto+OpenGroup.swift */, ); path = Crypto; sourceTree = ""; @@ -4278,7 +4266,6 @@ FD37E9F728A5F143003AE748 /* Migrations */ = { isa = PBXGroup; children = ( - FD37E9F828A5F14A003AE748 /* _001_ThemePreferences.swift */, ); path = Migrations; sourceTree = ""; @@ -4366,6 +4353,206 @@ path = Models; sourceTree = ""; }; + FD6B92892E779D8D004463B5 /* SOGS */ = { + isa = PBXGroup; + children = ( + FD6B92C32E77ACF2004463B5 /* Crypto */, + FD6B92A82E77A8B2004463B5 /* Models */, + FD6B92A72E77A875004463B5 /* Types */, + FD6B92A92E77A8F8004463B5 /* SOGS.swift */, + B88FA7B726045D100049422F /* SOGSAPI.swift */, + FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */, + FDC4380827B31D4E00C60D73 /* SOGSError.swift */, + ); + path = SOGS; + sourceTree = ""; + }; + FD6B928A2E779DB6004463B5 /* FileServer */ = { + isa = PBXGroup; + children = ( + FD6B92C42E77AD01004463B5 /* Crypto */, + FD6B92A42E77A37A004463B5 /* Models */, + FD6B928B2E779DC8004463B5 /* FileServer.swift */, + FD6B928F2E779EDA004463B5 /* FileServerAPI.swift */, + FD6B928D2E779E95004463B5 /* FileServerEndpoint.swift */, + ); + path = FileServer; + sourceTree = ""; + }; + FD6B92952E77A036004463B5 /* Models */ = { + isa = PBXGroup; + children = ( + FD6B929C2E77A095004463B5 /* Info.swift */, + FD6B929A2E77A083004463B5 /* NetworkInfo.swift */, + FD6B92962E77A042004463B5 /* Price.swift */, + FD6B92982E77A06C004463B5 /* Token.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD6B929E2E77A0E8004463B5 /* Types */ = { + isa = PBXGroup; + children = ( + 941375BA2D5184B60058F244 /* HTTPHeader+SessionNetwork.swift */, + 947D7FD22D509FC900E8E413 /* HTTPClient.swift */, + 947D7FD02D509FC900E8E413 /* KeyValueStore+SessionNetwork.swift */, + ); + path = Types; + sourceTree = ""; + }; + FD6B92A12E77A153004463B5 /* Types */ = { + isa = PBXGroup; + children = ( + FD19363B2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift */, + FD47E0B42AA6D7AA00A55E41 /* Request+SnodeAPI.swift */, + FDF848B429405C5A007DCAE5 /* SnodeMessage.swift */, + FDF848AA29405C5A007DCAE5 /* SnodeReceivedMessage.swift */, + ); + path = Types; + sourceTree = ""; + }; + FD6B92A42E77A37A004463B5 /* Models */ = { + isa = PBXGroup; + children = ( + FDC4383727B3863200C60D73 /* AppVersionResponse.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD6B92A52E77A3BD004463B5 /* Models */ = { + isa = PBXGroup; + children = ( + FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD6B92A72E77A875004463B5 /* Types */ = { + isa = PBXGroup; + children = ( + FDF8487D29405993007DCAE5 /* HTTPHeader+SOGS.swift */, + FDF8487E29405994007DCAE5 /* HTTPQueryParam+SOGS.swift */, + FDC4381627B32EC700C60D73 /* Personalization.swift */, + FD02CC132C3677E6009AB976 /* Request+SOGS.swift */, + 7B81682228A4C1210069F315 /* UpdateTypes.swift */, + ); + path = Types; + sourceTree = ""; + }; + FD6B92A82E77A8B2004463B5 /* Models */ = { + isa = PBXGroup; + children = ( + FDC4386627B4E10E00C60D73 /* CapabilitiesResponse.swift */, + FDC4385C27B4C18900C60D73 /* Room.swift */, + FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */, + FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */, + FDC4387727B5C35400C60D73 /* SendSOGSMessageRequest.swift */, + FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */, + FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */, + FDC438C627BB6DF000C60D73 /* DirectMessage.swift */, + FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */, + FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */, + FDD20C192A0A03AC003898FB /* DeleteInboxResponse.swift */, + FDC438A327BB107F00C60D73 /* UserBanRequest.swift */, + FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */, + FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */, + 7B81682928B6F1420069F315 /* ReactionResponse.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD6B92C32E77ACF2004463B5 /* Crypto */ = { + isa = PBXGroup; + children = ( + FD6B92C72E77AD35004463B5 /* Crypto+SOGS.swift */, + ); + path = Crypto; + sourceTree = ""; + }; + FD6B92C42E77AD01004463B5 /* Crypto */ = { + isa = PBXGroup; + children = ( + FD6B92C52E77AD0B004463B5 /* Crypto+FileServer.swift */, + ); + path = Crypto; + sourceTree = ""; + }; + FD6B92C92E77B1A7004463B5 /* SOGS */ = { + isa = PBXGroup; + children = ( + FD6B92D92E77B58B004463B5 /* Crypto */, + FD6B92CA2E77B1AE004463B5 /* Models */, + FD6B92D52E77B54B004463B5 /* Types */, + FDC4389927BA002500C60D73 /* SOGSAPISpec.swift */, + ); + path = SOGS; + sourceTree = ""; + }; + FD6B92CA2E77B1AE004463B5 /* Models */ = { + isa = PBXGroup; + children = ( + FD6B92CB2E77B1E5004463B5 /* CapabilitiesResponse.swift */, + FDC2908627D7047F005DAE71 /* RoomSpec.swift */, + FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */, + FDC2909027D709CA005DAE71 /* SOGSMessageSpec.swift */, + FDC2908A27D707F3005DAE71 /* SendSOGSMessageRequestSpec.swift */, + FDC2908E27D70938005DAE71 /* SendDirectMessageRequestSpec.swift */, + FDC2908C27D70905005DAE71 /* UpdateMessageRequestSpec.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD6B92D52E77B54B004463B5 /* Types */ = { + isa = PBXGroup; + children = ( + FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */, + FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */, + FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */, + ); + path = Types; + sourceTree = ""; + }; + FD6B92D92E77B58B004463B5 /* Crypto */ = { + isa = PBXGroup; + children = ( + FD6B92DC2E77BB7E004463B5 /* Authentication+SOGS.swift */, + FD6B92DA2E77B592004463B5 /* CryptoSOGSAPISpec.swift */, + ); + path = Crypto; + sourceTree = ""; + }; + FD6B92DF2E77C1CB004463B5 /* PushNotification */ = { + isa = PBXGroup; + children = ( + FD6B92F52E77C6AF004463B5 /* Crypto */, + FDC4382D27B383A600C60D73 /* Models */, + FD6B92E52E77C33B004463B5 /* Types */, + FD6B92E02E77C1DC004463B5 /* PushNotification.swift */, + C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */, + FDC13D4F2A16EE50007267C7 /* PushNotificationEndpoint.swift */, + ); + path = PushNotification; + sourceTree = ""; + }; + FD6B92E52E77C33B004463B5 /* Types */ = { + isa = PBXGroup; + children = ( + FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */, + FD02CC112C367761009AB976 /* Request+PushNotificationAPI.swift */, + FDC13D482A16EC20007267C7 /* Service.swift */, + FD3765F52ADE5BA500DC1489 /* ServiceInfo.swift */, + ); + path = Types; + sourceTree = ""; + }; + FD6B92F52E77C6AF004463B5 /* Crypto */ = { + isa = PBXGroup; + children = ( + FD6B92F62E77C6D3004463B5 /* Crypto+PushNotification.swift */, + ); + path = Crypto; + sourceTree = ""; + }; FD7115F528C8150600B47552 /* Combine */ = { isa = PBXGroup; children = ( @@ -4455,7 +4642,6 @@ isa = PBXGroup; children = ( FD71164928E3EA5B00B47552 /* DismissType.swift */, - FD12A83C2AD63BCC00EEBA0D /* EditableState.swift */, FD12A83E2AD63BDF00EEBA0D /* Navigatable.swift */, FD12A8402AD63BEA00EEBA0D /* NavigatableState.swift */, FD12A8422AD63BF600EEBA0D /* ObservableTableSource.swift */, @@ -4490,7 +4676,7 @@ FD72BDA52BE369B600CF6CF6 /* Crypto */ = { isa = PBXGroup; children = ( - FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupAPISpec.swift */, + FD72BDA62BE369DC00CF6CF6 /* CryptoOpenGroupSpec.swift */, ); path = Crypto; sourceTree = ""; @@ -4617,14 +4803,8 @@ FD83B9C127CF33EE005E1583 /* Models */ = { isa = PBXGroup; children = ( - FD83B9C627CF3F10005E1583 /* CapabilitiesSpec.swift */, + FD83B9C627CF3F10005E1583 /* CapabilitySpec.swift */, FD83B9C427CF3E2A005E1583 /* OpenGroupSpec.swift */, - FDC2908627D7047F005DAE71 /* RoomSpec.swift */, - FDC2908827D70656005DAE71 /* RoomPollInfoSpec.swift */, - FDC2909027D709CA005DAE71 /* SOGSMessageSpec.swift */, - FDC2908A27D707F3005DAE71 /* SendMessageRequestSpec.swift */, - FDC2908E27D70938005DAE71 /* SendDirectMessageRequestSpec.swift */, - FDC2908C27D70905005DAE71 /* UpdateMessageRequestSpec.swift */, ); path = Models; sourceTree = ""; @@ -4653,14 +4833,6 @@ path = SessionNetworkScreen; sourceTree = ""; }; - FD8A5B232DC05A0E004C689B /* Recovered References */ = { - isa = PBXGroup; - children = ( - 941375BE2D5196D10058F244 /* Number+Utilities.swift */, - ); - name = "Recovered References"; - sourceTree = ""; - }; FD8ECF7529340F4800C0D1BB /* LibSession */ = { isa = PBXGroup; children = ( @@ -4762,15 +4934,16 @@ path = "Group Update Messages"; sourceTree = ""; }; - FDB5DAFB2A981C43002C8721 /* SessionSnodeKitTests */ = { + FDB5DAFB2A981C43002C8721 /* SessionNetworkingKitTests */ = { isa = PBXGroup; children = ( - FD66CB272BF3449B00268FAB /* SessionSnodeKit.xctestplan */, + FD66CB272BF3449B00268FAB /* SessionNetworkingKit.xctestplan */, FD3765DD2AD8F02300DC1489 /* _TestUtilities */, + FD6B92C92E77B1A7004463B5 /* SOGS */, FDAA16792AC28E2200DDBF77 /* Models */, FD2272C52C34E9D1004D8A6C /* Types */, ); - path = SessionSnodeKitTests; + path = SessionNetworkingKitTests; sourceTree = ""; }; FDC13D4E2A16EE41007267C7 /* Types */ = { @@ -4780,11 +4953,6 @@ FD981BD62DC9A61600564172 /* NotificationCategory.swift */, FDB11A4B2DCC527900BEF49F /* NotificationContent.swift */, FD981BD82DC9A69000564172 /* NotificationUserInfoKey.swift */, - FDC13D482A16EC20007267C7 /* Service.swift */, - FD3765F52ADE5BA500DC1489 /* ServiceInfo.swift */, - FDD82C3E2A205D0A00425F05 /* ProcessResult.swift */, - FDC13D4F2A16EE50007267C7 /* PushNotificationAPIEndpoint.swift */, - FD02CC112C367761009AB976 /* Request+PushNotificationAPI.swift */, ); path = Types; sourceTree = ""; @@ -4808,51 +4976,12 @@ path = LibSession; sourceTree = ""; }; - FDC2909227D710A9005DAE71 /* Types */ = { - isa = PBXGroup; - children = ( - FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */, - FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */, - FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */, - ); - path = Types; - sourceTree = ""; - }; - FDC4380727B31D3A00C60D73 /* Types */ = { - isa = PBXGroup; - children = ( - FDF8487D29405993007DCAE5 /* HTTPHeader+OpenGroup.swift */, - FDF8487E29405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift */, - FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */, - FDC4381627B32EC700C60D73 /* Personalization.swift */, - FD02CC132C3677E6009AB976 /* Request+OpenGroupAPI.swift */, - FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */, - 7B81682228A4C1210069F315 /* UpdateTypes.swift */, - ); - path = Types; - sourceTree = ""; - }; - FDC4381827B34EAD00C60D73 /* Models */ = { + FDC4381827B34EAD00C60D73 /* Types */ = { isa = PBXGroup; children = ( - FDC4386627B4E10E00C60D73 /* Capabilities.swift */, - FDC4385C27B4C18900C60D73 /* Room.swift */, - FDC4386427B4DE7600C60D73 /* RoomPollInfo.swift */, - FDC4385E27B4C4A200C60D73 /* PinnedMessage.swift */, - FDC4387727B5C35400C60D73 /* SendMessageRequest.swift */, - FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */, - FDC4386227B4D94E00C60D73 /* SOGSMessage.swift */, - FDC438C627BB6DF000C60D73 /* DirectMessage.swift */, - FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */, - FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */, - FDD20C192A0A03AC003898FB /* DeleteInboxResponse.swift */, - FDC438A327BB107F00C60D73 /* UserBanRequest.swift */, - FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */, - FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */, - 7B81682928B6F1420069F315 /* ReactionResponse.swift */, 7B81682B28B72F480069F315 /* PendingChange.swift */, ); - path = Models; + path = Types; sourceTree = ""; }; FDC4382D27B383A600C60D73 /* Models */ = { @@ -4863,11 +4992,6 @@ FDC13D4A2A16ECBA007267C7 /* SubscribeResponse.swift */, FDC13D552A171FE4007267C7 /* UnsubscribeRequest.swift */, FDC13D572A17207D007267C7 /* UnsubscribeResponse.swift */, - FD6E4C892A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift */, - FDC13D532A16FF29007267C7 /* LegacyGroupRequest.swift */, - FDCD2E022A41294E00964D6A /* LegacyGroupOnlyRequest.swift */, - FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */, - FDC13D592A1721C5007267C7 /* LegacyNotifyRequest.swift */, FDFBB74C2A1F3C4E00CA7350 /* NotificationMetadata.swift */, ); path = Models; @@ -4896,8 +5020,6 @@ children = ( FD72BDA52BE369B600CF6CF6 /* Crypto */, FD83B9C127CF33EE005E1583 /* Models */, - FDC2909227D710A9005DAE71 /* Types */, - FDC4389927BA002500C60D73 /* OpenGroupAPISpec.swift */, FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */, ); path = "Open Groups"; @@ -4907,7 +5029,7 @@ isa = PBXGroup; children = ( FD336F562CAA28CF00C0B51B /* CommonSMKMockExtensions.swift */, - FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift */, + FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift */, FD336F6E2CAA37CB00C0B51B /* MockCommunityPoller.swift */, FD336F582CAA28CF00C0B51B /* MockCommunityPollerCache.swift */, FD336F592CAA28CF00C0B51B /* MockDisplayPictureCache.swift */, @@ -4947,6 +5069,17 @@ path = JobRunner; sourceTree = ""; }; + FDE71B092E7934DC0023F5F9 /* DeveloperSettings */ = { + isa = PBXGroup; + children = ( + FDC1BD672CFE6EEA002CDC71 /* DeveloperSettingsViewModel.swift */, + FD860CBD2D6E7DA000BBE29C /* DeveloperSettingsViewModel+Testing.swift */, + FDE71B0A2E7935250023F5F9 /* DeveloperSettingsGroupsViewModel.swift */, + FDE71B0C2E793B1F0023F5F9 /* DeveloperSettingsProViewModel.swift */, + ); + path = DeveloperSettings; + sourceTree = ""; + }; FDE7214E287E50D50093DF33 /* Scripts */ = { isa = PBXGroup; children = ( @@ -4976,7 +5109,7 @@ FDE754E22C9BAFF4002A2623 /* Crypto */ = { isa = PBXGroup; children = ( - FDE754E12C9BAFF4002A2623 /* Crypto+SessionSnodeKit.swift */, + FDE754E12C9BAFF4002A2623 /* Crypto+SessionNetworkingKit.swift */, ); path = Crypto; sourceTree = ""; @@ -5103,14 +5236,10 @@ FD3765E82ADE1AAE00DC1489 /* UnrevokeSubaccountResponse.swift */, FDF848BA29405C5A007DCAE5 /* RevokeSubaccountRequest.swift */, FD02CC152C3681EF009AB976 /* RevokeSubaccountResponse.swift */, - FDF848AA29405C5A007DCAE5 /* SnodeReceivedMessage.swift */, - FDF848B429405C5A007DCAE5 /* SnodeMessage.swift */, FDF848AB29405C5A007DCAE5 /* GetNetworkTimestampResponse.swift */, FDF848A029405C5A007DCAE5 /* OxenDaemonRPCRequest.swift */, FDF848A629405C5A007DCAE5 /* ONSResolveRequest.swift */, FDF8489E29405C5A007DCAE5 /* ONSResolveResponse.swift */, - FDC4387127B5BB3B00C60D73 /* FileUploadResponse.swift */, - FDC4383727B3863200C60D73 /* AppVersionResponse.swift */, ); path = Models; sourceTree = ""; @@ -5177,7 +5306,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - C3C2A5A3255385C100C340D1 /* SessionSnodeKit.h in Headers */, + C3C2A5A3255385C100C340D1 /* SessionNetworkingKit.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5298,9 +5427,9 @@ productReference = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; productType = "com.apple.product-type.framework"; }; - C3C2A59E255385C100C340D1 /* SessionSnodeKit */ = { + C3C2A59E255385C100C340D1 /* SessionNetworkingKit */ = { isa = PBXNativeTarget; - buildConfigurationList = C3C2A5AA255385C100C340D1 /* Build configuration list for PBXNativeTarget "SessionSnodeKit" */; + buildConfigurationList = C3C2A5AA255385C100C340D1 /* Build configuration list for PBXNativeTarget "SessionNetworkingKit" */; buildPhases = ( C3C2A59A255385C100C340D1 /* Headers */, C3C2A59B255385C100C340D1 /* Sources */, @@ -5313,12 +5442,12 @@ FDB348822BE86A4400B716C2 /* PBXTargetDependency */, FD7F74622BAAA4C7006DDFD8 /* PBXTargetDependency */, ); - name = SessionSnodeKit; + name = SessionNetworkingKit; packageProductDependencies = ( FD6673F72D7021F200041530 /* SessionUtil */, ); productName = SessionSnodeKit; - productReference = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; + productReference = C3C2A59F255385C100C340D1 /* SessionNetworkingKit.framework */; productType = "com.apple.product-type.framework"; }; C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */ = { @@ -5456,9 +5585,9 @@ productReference = FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - FDB5DAF92A981C42002C8721 /* SessionSnodeKitTests */ = { + FDB5DAF92A981C42002C8721 /* SessionNetworkingKitTests */ = { isa = PBXNativeTarget; - buildConfigurationList = FD2272522C32910F004D8A6C /* Build configuration list for PBXNativeTarget "SessionSnodeKitTests" */; + buildConfigurationList = FD2272522C32910F004D8A6C /* Build configuration list for PBXNativeTarget "SessionNetworkingKitTests" */; buildPhases = ( FDB5DAF62A981C42002C8721 /* Sources */, FD01504F2CA2445E005B08A1 /* Frameworks */, @@ -5469,9 +5598,9 @@ dependencies = ( FDB5DB002A981C43002C8721 /* PBXTargetDependency */, ); - name = SessionSnodeKitTests; + name = SessionNetworkingKitTests; productName = SessionSnodeKitTests; - productReference = FDB5DAFA2A981C42002C8721 /* SessionSnodeKitTests.xctest */; + productReference = FDB5DAFA2A981C42002C8721 /* SessionNetworkingKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; FDC4388D27B9FFC700C60D73 /* SessionMessagingKitTests */ = { @@ -5642,11 +5771,11 @@ C33FD9AA255A548A00E217F9 /* SignalUtilitiesKit */, C331FF1A2558F9D300070591 /* SessionUIKit */, C3C2A6EF25539DE700C340D1 /* SessionMessagingKit */, - C3C2A59E255385C100C340D1 /* SessionSnodeKit */, + C3C2A59E255385C100C340D1 /* SessionNetworkingKit */, C3C2A678255388CC00C340D1 /* SessionUtilitiesKit */, FD71160828D00BAE00B47552 /* SessionTests */, FDC4388D27B9FFC700C60D73 /* SessionMessagingKitTests */, - FDB5DAF92A981C42002C8721 /* SessionSnodeKitTests */, + FDB5DAF92A981C42002C8721 /* SessionNetworkingKitTests */, FD83B9AE27CF200A005E1583 /* SessionUtilitiesKitTests */, ); }; @@ -5744,6 +5873,7 @@ C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */, FDEF57712C44D2D300131302 /* GeoLite2-Country-Blocks-IPv4 in Resources */, B8D07405265C683300F77E07 /* ElegantIcons.ttf in Resources */, + FDE71B0F2E7A195B0023F5F9 /* Session - Anonymous Messenger.storekit in Resources */, 45B74A7E2044AAB600CD42F8 /* complete.aifc in Resources */, 9473386E2BDF5F3E00B9E169 /* InfoPlist.xcstrings in Resources */, B8CCF6352396005F0091D419 /* SpaceMono-Regular.ttf in Resources */, @@ -5791,7 +5921,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - FD66CB2A2BF3449B00268FAB /* SessionSnodeKit.xctestplan in Resources */, + FD66CB2A2BF3449B00268FAB /* SessionNetworkingKit.xctestplan in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6065,7 +6195,6 @@ 7BAF54D427ACCF01003D12F8 /* SAEScreenLockViewController.swift in Sources */, B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */, FD78EA0A2DDFE45E00D55B50 /* Interaction+UI.swift in Sources */, - FD2272DE2C34F11F004D8A6C /* _001_ThemePreferences.swift in Sources */, 7BAF54D327ACCF01003D12F8 /* ShareAppExtensionContext.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -6227,80 +6356,120 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FD6B92E12E77C1E1004463B5 /* PushNotification.swift in Sources */, FD2272B12C33E337004D8A6C /* ProxiedContentDownloader.swift in Sources */, FDF848C329405C5A007DCAE5 /* DeleteMessagesRequest.swift in Sources */, + FD6B928E2E779E99004463B5 /* FileServerEndpoint.swift in Sources */, FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */, FD2272AB2C33E337004D8A6C /* ValidatableResponse.swift in Sources */, FD2272B72C33E337004D8A6C /* Request.swift in Sources */, FD2272B92C33E337004D8A6C /* ResponseInfo.swift in Sources */, - C3C2A5E02553860B00C340D1 /* Threading+SSK.swift in Sources */, + FD6B92F82E77C725004463B5 /* ProcessResult.swift in Sources */, + FD6B92902E779EDD004463B5 /* FileServerAPI.swift in Sources */, FDF848D729405C5B007DCAE5 /* SnodeBatchRequest.swift in Sources */, FDF848CE29405C5B007DCAE5 /* UpdateExpiryAllRequest.swift in Sources */, FDF848C229405C5A007DCAE5 /* OxenDaemonRPCRequest.swift in Sources */, FDF848DC29405C5B007DCAE5 /* RevokeSubaccountRequest.swift in Sources */, - FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */, FDF848D029405C5B007DCAE5 /* UpdateExpiryResponse.swift in Sources */, + FD6B92E82E77C5B7004463B5 /* PushNotificationEndpoint.swift in Sources */, + FD6B92AC2E77A993004463B5 /* SOGSEndpoint.swift in Sources */, + FD6B92922E779FC8004463B5 /* SessionNetwork.swift in Sources */, FDF848D329405C5B007DCAE5 /* UpdateExpiryAllResponse.swift in Sources */, FDD20C162A09E64A003898FB /* GetExpiriesRequest.swift in Sources */, FDF848BC29405C5A007DCAE5 /* SnodeRecursiveResponse.swift in Sources */, FDF848C029405C5A007DCAE5 /* ONSResolveResponse.swift in Sources */, FD2272AC2C33E337004D8A6C /* IPv4.swift in Sources */, - FD17D7A427F40F8100122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, FD2272D82C34EDE7004D8A6C /* SnodeAPIEndpoint.swift in Sources */, FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */, + FD6B92992E77A06E004463B5 /* Token.swift in Sources */, FDB5DAF32A96DD4F002C8721 /* PreparedRequest+Sending.swift in Sources */, FDF848C629405C5B007DCAE5 /* DeleteAllMessagesRequest.swift in Sources */, FDF848D429405C5B007DCAE5 /* DeleteAllBeforeResponse.swift in Sources */, + FD6B92AF2E77AA03004463B5 /* HTTPQueryParam+SOGS.swift in Sources */, + FD6B92B02E77AA03004463B5 /* Request+SOGS.swift in Sources */, + FD6B92B12E77AA03004463B5 /* HTTPHeader+SOGS.swift in Sources */, + FD6B92F72E77C6D7004463B5 /* Crypto+PushNotification.swift in Sources */, + FD6B92B22E77AA03004463B5 /* UpdateTypes.swift in Sources */, + FD6B92B32E77AA03004463B5 /* Personalization.swift in Sources */, + FD6B929B2E77A084004463B5 /* NetworkInfo.swift in Sources */, FD47E0B52AA6D7AA00A55E41 /* Request+SnodeAPI.swift in Sources */, + FDE71B052E77E1AA0023F5F9 /* ObservableKey+SessionNetworkingKit.swift in Sources */, FD5E93D12C100FD70038C25A /* FileUploadResponse.swift in Sources */, FDF848D629405C5B007DCAE5 /* SnodeMessage.swift in Sources */, FD02CC162C3681EF009AB976 /* RevokeSubaccountResponse.swift in Sources */, - FDE754E32C9BAFF4002A2623 /* Crypto+SessionSnodeKit.swift in Sources */, + FDE754E32C9BAFF4002A2623 /* Crypto+SessionNetworkingKit.swift in Sources */, FD2272AD2C33E337004D8A6C /* Network.swift in Sources */, FD2272B32C33E337004D8A6C /* BatchRequest.swift in Sources */, FDF848D129405C5B007DCAE5 /* SnodeSwarmItem.swift in Sources */, FDF848DD29405C5B007DCAE5 /* LegacySendMessageRequest.swift in Sources */, FDF848BD29405C5A007DCAE5 /* GetMessagesRequest.swift in Sources */, FD2272B02C33E337004D8A6C /* NetworkError.swift in Sources */, + FD6B92AB2E77A920004463B5 /* SOGS.swift in Sources */, FD19363C2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift in Sources */, - 947D7FD42D509FC900E8E413 /* SessionNetworkAPI+Models.swift in Sources */, + FD6B92E92E77C5D1004463B5 /* SubscribeResponse.swift in Sources */, + FD6B92EA2E77C5D1004463B5 /* NotificationMetadata.swift in Sources */, + FD6B92EB2E77C5D1004463B5 /* AuthenticatedRequest.swift in Sources */, + FD6B92EF2E77C5D1004463B5 /* UnsubscribeRequest.swift in Sources */, + FD6B92F02E77C5D1004463B5 /* SubscribeRequest.swift in Sources */, + FD6B92F22E77C5D1004463B5 /* UnsubscribeResponse.swift in Sources */, 947D7FD62D509FC900E8E413 /* SessionNetworkAPI.swift in Sources */, - 947D7FD72D509FC900E8E413 /* SessionNetworkAPI+Network.swift in Sources */, - 947D7FD82D509FC900E8E413 /* SessionNetworkAPI+Database.swift in Sources */, + 947D7FD72D509FC900E8E413 /* HTTPClient.swift in Sources */, + FD6B92B42E77AA11004463B5 /* PinnedMessage.swift in Sources */, + FD6B92B52E77AA11004463B5 /* SendDirectMessageResponse.swift in Sources */, + FD6B92B62E77AA11004463B5 /* UserUnbanRequest.swift in Sources */, + FD6B92B72E77AA11004463B5 /* UserModeratorRequest.swift in Sources */, + FD6B92B82E77AA11004463B5 /* SendSOGSMessageRequest.swift in Sources */, + FD6B92B92E77AA11004463B5 /* Room.swift in Sources */, + FD6B92BA2E77AA11004463B5 /* RoomPollInfo.swift in Sources */, + FD6B92BB2E77AA11004463B5 /* UpdateMessageRequest.swift in Sources */, + FD6B92BC2E77AA11004463B5 /* ReactionResponse.swift in Sources */, + FD6B92BD2E77AA11004463B5 /* UserBanRequest.swift in Sources */, + FD6B92BE2E77AA11004463B5 /* DirectMessage.swift in Sources */, + FD6B92BF2E77AA11004463B5 /* DeleteInboxResponse.swift in Sources */, + FD6B92C02E77AA11004463B5 /* SendDirectMessageRequest.swift in Sources */, + FD6B92C12E77AA11004463B5 /* CapabilitiesResponse.swift in Sources */, + FD6B92C22E77AA11004463B5 /* SOGSMessage.swift in Sources */, + 947D7FD82D509FC900E8E413 /* KeyValueStore+SessionNetwork.swift in Sources */, FDF848DB29405C5B007DCAE5 /* DeleteMessagesResponse.swift in Sources */, + FD6B92F42E77C61A004463B5 /* ServiceInfo.swift in Sources */, FDF848E629405D6E007DCAE5 /* Destination.swift in Sources */, + FD6B92A32E77A18B004463B5 /* SnodeAPI.swift in Sources */, FDF848CC29405C5B007DCAE5 /* SnodeReceivedMessage.swift in Sources */, FDF848C129405C5A007DCAE5 /* UpdateExpiryRequest.swift in Sources */, + FD6B92E22E77C21D004463B5 /* PushNotificationAPI.swift in Sources */, FDF848C729405C5B007DCAE5 /* SendMessageResponse.swift in Sources */, FDF848CA29405C5B007DCAE5 /* DeleteAllBeforeRequest.swift in Sources */, FD2272AF2C33E337004D8A6C /* JSON.swift in Sources */, FD2272D62C34ED6A004D8A6C /* RetryWithDependencies.swift in Sources */, FDF848D229405C5B007DCAE5 /* LegacyGetMessagesRequest.swift in Sources */, - FD8A5B342DC1A732004C689B /* _008_ResetUserConfigLastHashes.swift in Sources */, FDF848E529405D6E007DCAE5 /* SnodeAPIError.swift in Sources */, - FD61FCFB2D34A5EA005752DE /* _007_SplitSnodeReceivedMessageInfo.swift in Sources */, + FD6B928C2E779DCC004463B5 /* FileServer.swift in Sources */, FDF848D529405C5B007DCAE5 /* DeleteAllMessagesResponse.swift in Sources */, FD2272B22C33E337004D8A6C /* PreparedRequest.swift in Sources */, FDF848BF29405C5A007DCAE5 /* SnodeResponse.swift in Sources */, + FD6B92C82E77AD39004463B5 /* Crypto+SOGS.swift in Sources */, + FD6B92942E77A003004463B5 /* SessionNetworkEndpoint.swift in Sources */, FDD20C182A09E7D3003898FB /* GetExpiriesResponse.swift in Sources */, FD2272BB2C33E337004D8A6C /* HTTPMethod.swift in Sources */, + FD6B92AE2E77A9F7004463B5 /* SOGSAPI.swift in Sources */, + FD6B92972E77A047004463B5 /* Price.swift in Sources */, C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */, FDF848D929405C5B007DCAE5 /* SnodeAuthenticatedRequestBody.swift in Sources */, + FD6B92AD2E77A9F1004463B5 /* SOGSError.swift in Sources */, FD2272BA2C33E337004D8A6C /* HTTPHeader.swift in Sources */, FDF848CD29405C5B007DCAE5 /* GetNetworkTimestampResponse.swift in Sources */, FDF848DA29405C5B007DCAE5 /* GetMessagesResponse.swift in Sources */, - FD39353628F7C3390084DADA /* _004_FlagMessageHashAsDeletedOrInvalid.swift in Sources */, - FD7F74572BAA9D31006DDFD8 /* _006_DropSnodeCache.swift in Sources */, - FDF8489429405C1B007DCAE5 /* SnodeAPI.swift in Sources */, + FD6B92C62E77AD0F004463B5 /* Crypto+FileServer.swift in Sources */, FD2286682C37DA3B00BC06F7 /* LibSession+Networking.swift in Sources */, FD2272A92C33E337004D8A6C /* ContentProxy.swift in Sources */, + FD6B92E62E77C5A2004463B5 /* Service.swift in Sources */, + FD6B92E72E77C5A2004463B5 /* Request+PushNotificationAPI.swift in Sources */, + FD6B92DE2E77BDE2004463B5 /* Authentication+SOGS.swift in Sources */, FDF848C829405C5B007DCAE5 /* ONSResolveRequest.swift in Sources */, - FD6DF00B2ACFE40D0084BA4C /* _005_AddSnodeReveivedMessageInfoPrimaryKey.swift in Sources */, - C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */, + FD6B929D2E77A096004463B5 /* Info.swift in Sources */, FDF848C929405C5B007DCAE5 /* SnodeRequest.swift in Sources */, FDF848CF29405C5B007DCAE5 /* SendMessageRequest.swift in Sources */, FD2272BE2C34B710004D8A6C /* Publisher+Utilities.swift in Sources */, - FD6A7A6D2818C61500035AC1 /* _002_SetupStandardJobs.swift in Sources */, FD3765E72ADE1AA300DC1489 /* UnrevokeSubaccountRequest.swift in Sources */, FD2272BC2C33E337004D8A6C /* URLResponse+Utilities.swift in Sources */, FD2272B52C33E337004D8A6C /* BatchResponse.swift in Sources */, @@ -6321,7 +6490,6 @@ files = ( FD2272C82C34EB0A004D8A6C /* Job.swift in Sources */, FD2272C72C34EAF5004D8A6C /* ColumnExpressible.swift in Sources */, - 945D9C582D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift in Sources */, FDB3486E2BE8457F00B716C2 /* BackgroundTaskManager.swift in Sources */, 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */, FD428B192B4B576F006D0888 /* AppContext.swift in Sources */, @@ -6334,7 +6502,6 @@ FD42ECD62E3308B5002D03EA /* ObservableKey+SessionUtilitiesKit.swift in Sources */, FDE754D22C9BAF53002A2623 /* JobDependencies.swift in Sources */, FDE755242C9BC1D1002A2623 /* Publisher+Utilities.swift in Sources */, - FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */, 7B7CB192271508AD0079FF93 /* CallRingTonePlayer.swift in Sources */, FD00CDCB2D5317A7006B96D3 /* Scheduler+Utilities.swift in Sources */, FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */, @@ -6360,10 +6527,8 @@ FD5931A72A8DA5DA0040147D /* SQLInterpolation+Utilities.swift in Sources */, FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */, FD3765EA2ADE37B400DC1489 /* Authentication.swift in Sources */, - FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */, FDE755202C9BC1A6002A2623 /* CacheConfig.swift in Sources */, FD1A553E2E14BE11003761E4 /* PagedData.swift in Sources */, - FD4BB22C2D63FA8600D0DC3D /* (null) in Sources */, FDE755192C9BC169002A2623 /* UIImage+Utilities.swift in Sources */, C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */, FD97B2402A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift in Sources */, @@ -6377,10 +6542,8 @@ FDB7400B28EB99A70094D718 /* TimeInterval+Utilities.swift in Sources */, FD09797D27FBDB2000936362 /* Notification+Utilities.swift in Sources */, FDC6D7602862B3F600B04575 /* Dependencies.swift in Sources */, - FDE521902E04CCEB00061B8E /* AVURLAsset+Utilities.swift in Sources */, FD6673FF2D77F9C100041530 /* ScreenLock.swift in Sources */, FDB3DA8B2E24834000148F8D /* AVURLAsset+Utilities.swift in Sources */, - FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */, FD848B9328420164000E298B /* UnicodeScalar+Utilities.swift in Sources */, FDE754CE2C9BAF37002A2623 /* ImageFormat.swift in Sources */, FDB11A542DCD7A7F00BEF49F /* Task+Utilities.swift in Sources */, @@ -6390,11 +6553,9 @@ FDE33BBC2D5C124900E56F42 /* DispatchTimeInterval+Utilities.swift in Sources */, FD7115FA28C8153400B47552 /* UIBarButtonItem+Combine.swift in Sources */, FD705A92278D051200F16121 /* ReusableView.swift in Sources */, - FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */, FDE754DD2C9BAF8A002A2623 /* Mnemonic.swift in Sources */, FD52CB652E13B6E900A4DA70 /* ObservationBuilder.swift in Sources */, FDBEE52E2B6A18B900C143A0 /* UserDefaultsConfig.swift in Sources */, - 947D7FE32D5181F400E8E413 /* _006_RenameTableSettingToKeyValueStore.swift in Sources */, FD78EA042DDEC3C500D55B50 /* MultiTaskManager.swift in Sources */, FD78EA062DDEC8F600D55B50 /* AsyncSequence+Utilities.swift in Sources */, FDC438CD27BC641200C60D73 /* Set+Utilities.swift in Sources */, @@ -6432,7 +6593,6 @@ FDE754D42C9BAF6B002A2623 /* UICollectionView+ReusableView.swift in Sources */, FDE754C02C9BAEF6002A2623 /* Array+Utilities.swift in Sources */, FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */, - FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */, FDAA16762AC28A3B00DDBF77 /* UserDefaultsType.swift in Sources */, FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */, FD7F745B2BAAA35E006DDFD8 /* LibSession.swift in Sources */, @@ -6456,7 +6616,6 @@ FDF01FAD2A9ECC4200CAF969 /* SingletonConfig.swift in Sources */, FDE755182C9BC169002A2623 /* UIAlertAction+Utilities.swift in Sources */, FD7115F828C8151C00B47552 /* DisposableBarButtonItem.swift in Sources */, - FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, FDE7551B2C9BC169002A2623 /* UINavigationController+Utilities.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -6466,20 +6625,20 @@ buildActionMask = 2147483647; files = ( FD8ECF7B29340FFD00C0D1BB /* LibSession+SessionMessagingKit.swift in Sources */, - 7B81682828B310D50069F315 /* _007_HomeQueryOptimisationIndexes.swift in Sources */, + FDD23AE62E458CAA0057E853 /* _023_SplitSnodeReceivedMessageInfo.swift in Sources */, + 7B81682828B310D50069F315 /* _015_HomeQueryOptimisationIndexes.swift in Sources */, FD245C52285065D500B966DD /* SignalAttachment.swift in Sources */, FD2273002C352D8E004D8A6C /* LibSession+GroupKeys.swift in Sources */, - FD70F25C2DC1F184003729B7 /* _026_MessageDeduplicationTable.swift in Sources */, + FD70F25C2DC1F184003729B7 /* _040_MessageDeduplicationTable.swift in Sources */, FD2272FB2C352D8E004D8A6C /* LibSession+UserGroups.swift in Sources */, B8856D08256F10F1001CE70E /* DeviceSleepManager.swift in Sources */, FD22726F2C32911C004D8A6C /* ProcessPendingGroupMemberRemovalsJob.swift in Sources */, FD981BD72DC9A61A00564172 /* NotificationCategory.swift in Sources */, C300A5D32554B05A00555489 /* TypingIndicator.swift in Sources */, FDF71EA52B07363500A8D6B5 /* MessageReceiver+LibSession.swift in Sources */, - FDC13D582A17207D007267C7 /* UnsubscribeResponse.swift in Sources */, FD09799927FFC1A300936362 /* Attachment.swift in Sources */, FD245C5F2850662200B966DD /* OWSWindowManager.m in Sources */, - FDF40CDE2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift in Sources */, + FDF40CDE2897A1BC006A0CC4 /* _011_RemoveLegacyYDB.swift in Sources */, FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */, FD78E9F42DDABA4F00D55B50 /* AttachmentUploader.swift in Sources */, FDE754A32C9A8FD1002A2623 /* SwarmPoller.swift in Sources */, @@ -6493,80 +6652,71 @@ FD47E0B12AA6A05800A55E41 /* Authentication+SessionMessagingKit.swift in Sources */, FD2272832C337830004D8A6C /* GroupPoller.swift in Sources */, FD22726C2C32911C004D8A6C /* GroupLeavingJob.swift in Sources */, - FDE33BBE2D5C3AF100E56F42 /* _023_GroupsExpiredFlag.swift in Sources */, + FDE33BBE2D5C3AF100E56F42 /* _037_GroupsExpiredFlag.swift in Sources */, FDF848F729414477007DCAE5 /* CurrentUserPoller.swift in Sources */, C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */, FD2272FD2C352D8E004D8A6C /* LibSession+ConvoInfoVolatile.swift in Sources */, FD22727E2C32911C004D8A6C /* GarbageCollectionJob.swift in Sources */, FD09B7E7288670FD00ED0B66 /* Reaction.swift in Sources */, FD245C5A2850660100B966DD /* LinkPreviewDraft.swift in Sources */, - FDD82C3F2A205D0A00425F05 /* ProcessResult.swift in Sources */, FDE5219E2E0D0B9B00061B8E /* AsyncAccessible.swift in Sources */, FDF0B75C2807F41D004C14C5 /* MessageSender+Convenience.swift in Sources */, FD22726D2C32911C004D8A6C /* CheckForAppUpdatesJob.swift in Sources */, - 7B81682A28B6F1420069F315 /* ReactionResponse.swift in Sources */, FD2273082C353109004D8A6C /* DisplayPictureManager.swift in Sources */, FDE521A22E0D23AB00061B8E /* ObservableKey+SessionMessagingKit.swift in Sources */, FD2273022C352D8E004D8A6C /* LibSession+GroupInfo.swift in Sources */, - FDDD554E2C1FCB77006CBF03 /* _019_ScheduleAppUpdateCheckJob.swift in Sources */, + FDDD554E2C1FCB77006CBF03 /* _033_ScheduleAppUpdateCheckJob.swift in Sources */, FD09798927FD1C5A00936362 /* OpenGroup.swift in Sources */, - 94CD95C12E0CBF430097754D /* _029_AddProMessageFlag.swift in Sources */, + 94CD95C12E0CBF430097754D /* _044_AddProMessageFlag.swift in Sources */, FD2272FC2C352D8E004D8A6C /* LibSession+Contacts.swift in Sources */, FD848B9628422A2A000E298B /* MessageViewModel.swift in Sources */, FD05593D2DFA3A2800DC48CE /* VoipPayloadKey.swift in Sources */, FD2272782C32911C004D8A6C /* AttachmentDownloadJob.swift in Sources */, FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */, - FD428B232B4B9969006D0888 /* _017_RebuildFTSIfNeeded_2_4_5.swift in Sources */, + FD428B232B4B9969006D0888 /* _031_RebuildFTSIfNeeded_2_4_5.swift in Sources */, FD2272742C32911C004D8A6C /* ConfigMessageReceiveJob.swift in Sources */, - FDFBB74D2A1F3C4E00CA7350 /* NotificationMetadata.swift in Sources */, FD716E6628502EE200C96BF4 /* CurrentCallProtocol.swift in Sources */, FD22727A2C32911C004D8A6C /* GroupInviteMemberJob.swift in Sources */, - FDB5DAC72A9447E7002C8721 /* _022_GroupsRebuildChanges.swift in Sources */, - FD09B7E5288670BB00ED0B66 /* _008_EmojiReacts.swift in Sources */, - FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */, - FDD20C1A2A0A03AC003898FB /* DeleteInboxResponse.swift in Sources */, + FDB5DAC72A9447E7002C8721 /* _036_GroupsRebuildChanges.swift in Sources */, + FD09B7E5288670BB00ED0B66 /* _017_EmojiReacts.swift in Sources */, 7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */, - FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */, FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */, FD2272FA2C352D8E004D8A6C /* LibSession+SharedGroup.swift in Sources */, - FD37EA0F28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift in Sources */, + FD37EA0F28AB3330003AE748 /* _014_FixHiddenModAdminSupport.swift in Sources */, FD2272772C32911C004D8A6C /* AttachmentUploadJob.swift in Sources */, - 7B81682328A4C1210069F315 /* UpdateTypes.swift in Sources */, - FDC13D472A16E4CA007267C7 /* SubscribeRequest.swift in Sources */, - FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */, FD22727C2C32911C004D8A6C /* GroupPromoteMemberJob.swift in Sources */, FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */, - FDC4386727B4E10E00C60D73 /* Capabilities.swift in Sources */, - FDC438A427BB107F00C60D73 /* UserBanRequest.swift in Sources */, FDE754F22C9BB08B002A2623 /* Crypto+SessionMessagingKit.swift in Sources */, - FD4C53AF2CC1D62E003B10F4 /* _021_ReworkRecipientState.swift in Sources */, + FD4C53AF2CC1D62E003B10F4 /* _035_ReworkRecipientState.swift in Sources */, C379DCF4256735770002D4EB /* VisibleMessage+Attachment.swift in Sources */, FD6C67242CF6E72E00B350A7 /* NoopSessionCallManager.swift in Sources */, FDB4BBC72838B91E00B7C95D /* LinkPreviewError.swift in Sources */, FD09798327FD1A1500936362 /* ClosedGroup.swift in Sources */, - FDC13D542A16FF29007267C7 /* LegacyGroupRequest.swift in Sources */, B8B320B7258C30D70020074B /* HTMLMetadata.swift in Sources */, FD09798727FD1B7800936362 /* GroupMember.swift in Sources */, FD78EA0D2DDFEDE200D55B50 /* LibSession+Local.swift in Sources */, - FDCD2E032A41294E00964D6A /* LegacyGroupOnlyRequest.swift in Sources */, + FDD23AE12E457CDE0057E853 /* _005_SNK_SetupStandardJobs.swift in Sources */, FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */, FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */, FDE754F02C9BB08B002A2623 /* Crypto+Attachments.swift in Sources */, - FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, - FDE754A12C9A60A6002A2623 /* Crypto+OpenGroupAPI.swift in Sources */, + FD17D79927F40AB800122BE0 /* _009_SMK_YDBToGRDBMigration.swift in Sources */, + FDE754A12C9A60A6002A2623 /* Crypto+OpenGroup.swift in Sources */, FDF0B7512807BA56004C14C5 /* NotificationsManagerType.swift in Sources */, FD2272722C32911C004D8A6C /* FailedAttachmentDownloadsJob.swift in Sources */, - FDC13D5A2A1721C5007267C7 /* LegacyNotifyRequest.swift in Sources */, + FDD23AEA2E458EB00057E853 /* _012_AddJobPriority.swift in Sources */, FD245C59285065FC00B966DD /* ControlMessage.swift in Sources */, - FD6E4C8A2A1AEE4700C7C243 /* LegacyUnsubscribeRequest.swift in Sources */, B8DE1FB626C22FCB0079C9CE /* CallMessage.swift in Sources */, + FDD23AEC2E458F980057E853 /* _024_ResetUserConfigLastHashes.swift in Sources */, FD245C50285065C700B966DD /* VisibleMessage+Quote.swift in Sources */, + FDD23AE82E458DD40057E853 /* _002_SUK_SetupStandardJobs.swift in Sources */, FD72BDA12BE368C800CF6CF6 /* UIWindowLevel+Utilities.swift in Sources */, FD4C4E9C2B02E2A300C72199 /* DisplayPictureError.swift in Sources */, + FDD23AE22E457CE50057E853 /* _008_SNK_YDBToGRDBMigration.swift in Sources */, FD5C7307284F103B0029977D /* MessageReceiver+MessageRequests.swift in Sources */, C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */, FD3FAB592ADF906300DC5421 /* Profile+CurrentUser.swift in Sources */, FDEF573E2C40F2A100131302 /* GroupUpdateMemberLeftNotificationMessage.swift in Sources */, + FDD23ADF2E457CAA0057E853 /* _016_ThemePreferences.swift in Sources */, FDB11A5D2DD300D300BEF49F /* SNProtoContent+Utilities.swift in Sources */, FDE755002C9BB0FA002A2623 /* SessionEnvironment.swift in Sources */, FDB5DADC2A95D840002C8721 /* GroupUpdateMemberChangeMessage.swift in Sources */, @@ -6575,49 +6725,39 @@ FD8FD7622C37B7BD001E38C7 /* Position.swift in Sources */, 7B93D07127CF194000811CB6 /* MessageRequestResponse.swift in Sources */, FDB5DADE2A95D847002C8721 /* GroupUpdatePromoteMessage.swift in Sources */, - FD245C662850665900B966DD /* OpenGroupAPI.swift in Sources */, FD245C5B2850660500B966DD /* ReadReceipt.swift in Sources */, FD428B1F2B4B758B006D0888 /* AppReadiness.swift in Sources */, FD22726B2C32911C004D8A6C /* SendReadReceiptsJob.swift in Sources */, B8F5F60325EDE16F003BF8D4 /* DataExtractionNotification.swift in Sources */, C3A71D1E25589AC30043A11F /* WebSocketProto.swift in Sources */, C3C2A7852553AAF300C340D1 /* SessionProtos.pb.swift in Sources */, - FDF0B7422804EA4F004C14C5 /* _002_SetupStandardJobs.swift in Sources */, + FDF0B7422804EA4F004C14C5 /* _007_SMK_SetupStandardJobs.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, - FDF8487F29405994007DCAE5 /* HTTPHeader+OpenGroup.swift in Sources */, - FD8ECF7D2934293A00C0D1BB /* _013_SessionUtilChanges.swift in Sources */, - FD17D7A227F40F0500122BE0 /* _001_InitialSetupMigration.swift in Sources */, + FD8ECF7D2934293A00C0D1BB /* _027_SessionUtilChanges.swift in Sources */, + FD17D7A227F40F0500122BE0 /* _006_SMK_InitialSetupMigration.swift in Sources */, FD245C5D2850660F00B966DD /* OWSAudioPlayer.m in Sources */, FD2272FE2C352D8E004D8A6C /* LibSession+GroupMembers.swift in Sources */, FDF0B7582807F368004C14C5 /* MessageReceiverError.swift in Sources */, FD09798D27FD1D8900936362 /* DisappearingMessageConfiguration.swift in Sources */, FD2272732C32911C004D8A6C /* ConfigurationSyncJob.swift in Sources */, FDF0B75A2807F3A3004C14C5 /* MessageSenderError.swift in Sources */, - FDC4382F27B383AF00C60D73 /* LegacyPushServerResponse.swift in Sources */, 94B6BAFA2E38454F00E718BB /* SessionProState.swift in Sources */, - FDC4386327B4D94E00C60D73 /* SOGSMessage.swift in Sources */, FD245C692850666800B966DD /* ExpirationTimerUpdate.swift in Sources */, - FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */, FD2272752C32911C004D8A6C /* RetrieveDefaultOpenGroupRoomsJob.swift in Sources */, FD2272712C32911C004D8A6C /* MessageReceiveJob.swift in Sources */, FDB5DAC12A9443A5002C8721 /* MessageSender+Groups.swift in Sources */, - FDC13D4B2A16ECBA007267C7 /* SubscribeResponse.swift in Sources */, FD2272702C32911C004D8A6C /* DisappearingMessagesJob.swift in Sources */, - FD7115F228C6CB3900B47552 /* _010_AddThreadIdToFTS.swift in Sources */, + FD7115F228C6CB3900B47552 /* _019_AddThreadIdToFTS.swift in Sources */, FD716E6428502DDD00C96BF4 /* CallManagerProtocol.swift in Sources */, 943C6D822B75E061004ACE64 /* Message+DisappearingMessages.swift in Sources */, - FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */, - FD3765F42ADE5A0800DC1489 /* AuthenticatedRequest.swift in Sources */, - FDC13D502A16EE50007267C7 /* PushNotificationAPIEndpoint.swift in Sources */, FD432434299C6985008A0213 /* PendingReadReceipt.swift in Sources */, - FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */, FD78E9F62DDD43AD00D55B50 /* Mutation.swift in Sources */, FDB11A5F2DD5B77800BEF49F /* Message+Origin.swift in Sources */, FD245C51285065CC00B966DD /* MessageReceiver.swift in Sources */, - FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */, FDB5DAE62A95D8B0002C8721 /* GroupUpdateDeleteMemberContentMessage.swift in Sources */, - 7B5233C6290636D700F8F375 /* _018_DisappearingMessagesConfiguration.swift in Sources */, + 7B5233C6290636D700F8F375 /* _032_DisappearingMessagesConfiguration.swift in Sources */, FD5C72FD284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift in Sources */, + FDD23AEB2E458F4D0057E853 /* _020_AddJobUniqueHash.swift in Sources */, FDB11A502DCC6ADE00BEF49F /* ThreadUpdateInfo.swift in Sources */, B8D0A25925E367AC00C1835E /* Notification+MessageReceiver.swift in Sources */, FDC1BD662CFD6C4F002CDC71 /* Config.swift in Sources */, @@ -6626,56 +6766,52 @@ C32C599E256DB02B003C73A2 /* TypingIndicators.swift in Sources */, FDE7549B2C940108002A2623 /* MessageViewModel+DeletionActions.swift in Sources */, FD09799527FE7B8E00936362 /* Interaction.swift in Sources */, - FD37EA0D28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift in Sources */, - 7BAA7B6628D2DE4700AE1489 /* _009_OpenGroupPermission.swift in Sources */, - FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */, + FD37EA0D28AB2A45003AE748 /* _013_FixDeletedMessageReadState.swift in Sources */, + FDD23AE32E457CFE0057E853 /* _010_FlagMessageHashAsDeletedOrInvalid.swift in Sources */, + 7BAA7B6628D2DE4700AE1489 /* _018_OpenGroupPermission.swift in Sources */, FD2286692C37DA5500BC06F7 /* PollerType.swift in Sources */, - FD860CBC2D6E7A9F00BBE29C /* _024_FixBustedInteractionVariant.swift in Sources */, + FD860CBC2D6E7A9F00BBE29C /* _038_FixBustedInteractionVariant.swift in Sources */, + FDD23AE72E458DBC0057E853 /* _001_SUK_InitialSetupMigration.swift in Sources */, + FDD23AE42E458C810057E853 /* _021_AddSnodeReveivedMessageInfoPrimaryKey.swift in Sources */, FD22727B2C32911C004D8A6C /* MessageSendJob.swift in Sources */, FD78E9EE2DD6D32500D55B50 /* ImageDataManager+Singleton.swift in Sources */, - FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */, - FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */, C3A71D1F25589AC30043A11F /* WebSocketResources.pb.swift in Sources */, FD981BC42DC304E600564172 /* MessageDeduplication.swift in Sources */, FDF0B74B28061F7A004C14C5 /* InteractionAttachment.swift in Sources */, FD09796E27FA6D0000936362 /* Contact.swift in Sources */, - FD02CC142C3677E6009AB976 /* Request+OpenGroupAPI.swift in Sources */, FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */, - FDC13D492A16EC20007267C7 /* Service.swift in Sources */, FDBA8A842D597975007C19C0 /* FailedGroupInvitesAndPromotionsJob.swift in Sources */, - FD778B6429B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift in Sources */, - FDC13D562A171FE4007267C7 /* UnsubscribeRequest.swift in Sources */, + FD778B6429B189FF001BAC6B /* _028_GenerateInitialUserConfigDumps.swift in Sources */, FD1A55432E179AED003761E4 /* ObservableKeyEvent+Utilities.swift in Sources */, C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, - FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, + FDD23AE92E458E020057E853 /* _003_SUK_YDBToGRDBMigration.swift in Sources */, FD8ECF7F2934298100C0D1BB /* ConfigDump.swift in Sources */, FD22727F2C32911C004D8A6C /* GetExpirationJob.swift in Sources */, FD2272792C32911C004D8A6C /* DisplayPictureDownloadJob.swift in Sources */, FD981BD92DC9A69600564172 /* NotificationUserInfoKey.swift in Sources */, - C352A2FF25574B6300338F3E /* (null) in Sources */, + FDD23AE52E458C940057E853 /* _022_DropSnodeCache.swift in Sources */, FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */, B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, FD2272762C32911C004D8A6C /* ExpirationUpdateJob.swift in Sources */, FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */, - FD368A6829DE8F9C000DBF1E /* _012_AddFTSIfNeeded.swift in Sources */, + FD368A6829DE8F9C000DBF1E /* _026_AddFTSIfNeeded.swift in Sources */, FD5C7301284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift in Sources */, C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */, FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */, - FDFE75B12ABD2D2400655640 /* _016_MakeBrokenProfileTimestampsNullable.swift in Sources */, + FDFE75B12ABD2D2400655640 /* _030_MakeBrokenProfileTimestampsNullable.swift in Sources */, FD09799B27FFC82D00936362 /* Quote.swift in Sources */, FD2273012C352D8E004D8A6C /* LibSession+Shared.swift in Sources */, C3C2A74425539EB700C340D1 /* Message.swift in Sources */, FD245C682850666300B966DD /* Message+Destination.swift in Sources */, - FDF8488029405994007DCAE5 /* HTTPQueryParam+OpenGroup.swift in Sources */, - FD8A5B322DC191B4004C689B /* _025_DropLegacyClosedGroupKeyPairTable.swift in Sources */, + FD8A5B322DC191B4004C689B /* _039_DropLegacyClosedGroupKeyPairTable.swift in Sources */, FD245C632850664600B966DD /* Configuration.swift in Sources */, FD981BC62DC3310B00564172 /* ExtensionHelper.swift in Sources */, - FD78E9FA2DDD74D200D55B50 /* _027_MoveSettingsToLibSession.swift in Sources */, + FD78E9FA2DDD74D200D55B50 /* _042_MoveSettingsToLibSession.swift in Sources */, FD2272FF2C352D8E004D8A6C /* LibSession+UserProfile.swift in Sources */, FD5C7305284F0FF30029977D /* MessageReceiver+VisibleMessages.swift in Sources */, FDB5DAE82A95D96C002C8721 /* MessageReceiver+Groups.swift in Sources */, 94CD95BD2E09083C0097754D /* LibSession+Pro.swift in Sources */, - FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */, + FD1D732E2A86114600E3F410 /* _029_BlockCommunityMessageRequests.swift in Sources */, FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */, FDAA167F2AC5290000DDBF77 /* Preferences+NotificationPreviewType.swift in Sources */, FD09797027FA6FF300936362 /* Profile.swift in Sources */, @@ -6685,27 +6821,25 @@ FD09798B27FD1CFE00936362 /* Capability.swift in Sources */, C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */, FD09798127FCFEE800936362 /* SessionThread.swift in Sources */, - FD3765F62ADE5BA500DC1489 /* ServiceInfo.swift in Sources */, FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */, FDB5DADA2A95D839002C8721 /* GroupUpdateInfoChangeMessage.swift in Sources */, FDF71EA32B072C2800A8D6B5 /* LibSessionMessage.swift in Sources */, FDAA167D2AC528A200DDBF77 /* Preferences+Sound.swift in Sources */, - FDE754FE2C9BB0D0002A2623 /* Threading+SMK.swift in Sources */, + FDD23AED2E4590A10057E853 /* _041_RenameTableSettingToKeyValueStore.swift in Sources */, + FDE754FE2C9BB0D0002A2623 /* Threading+SessionMessagingKit.swift in Sources */, FDF0B75E280AAF35004C14C5 /* Preferences.swift in Sources */, FDF2F0222DAE1AF500491E8A /* MessageReceiver+LegacyClosedGroups.swift in Sources */, - FD02CC122C367762009AB976 /* Request+PushNotificationAPI.swift in Sources */, - FD05594E2E012D2700DC48CE /* _028_RenameAttachments.swift in Sources */, + FD05594E2E012D2700DC48CE /* _043_RenameAttachments.swift in Sources */, FD22726E2C32911C004D8A6C /* FailedMessageSendsJob.swift in Sources */, + FD6B92E42E77C256004463B5 /* PushNotificationAPI+SessionMessagingKit.swift in Sources */, FDB5DAD42A9483F3002C8721 /* GroupUpdateInviteMessage.swift in Sources */, FDB5DAE22A95D8A0002C8721 /* GroupUpdateInviteResponseMessage.swift in Sources */, FDB11A522DCC6B0000BEF49F /* OpenGroupUrlInfo.swift in Sources */, - FD3559462CC1FF200088F2A9 /* _020_AddMissingWhisperFlag.swift in Sources */, + FD3559462CC1FF200088F2A9 /* _034_AddMissingWhisperFlag.swift in Sources */, FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */, + FDD23AE02E457CD40057E853 /* _004_SNK_InitialSetupMigration.swift in Sources */, FD5C7303284F0FA50029977D /* MessageReceiver+Calls.swift in Sources */, - FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */, - FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */, - FDC4385D27B4C18900C60D73 /* Room.swift in Sources */, - FDE755022C9BB122002A2623 /* _011_AddPendingReadReceipts.swift in Sources */, + FDE755022C9BB122002A2623 /* _025_AddPendingReadReceipts.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6720,7 +6854,6 @@ FD7115EB28C5D78E00B47552 /* ThreadSettingsViewModel.swift in Sources */, B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */, 7B4EF25A2934743000CB351D /* SessionTableViewTitleView.swift in Sources */, - FD2272D92C34EED6004D8A6C /* _001_ThemePreferences.swift in Sources */, 942256A12C23F90700C0FDBF /* CustomTopTabBar.swift in Sources */, FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */, 4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */, @@ -6737,10 +6870,8 @@ B877E24626CA13BA0007970A /* CallVC+Camera.swift in Sources */, 454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */, FD3FAB5F2AE9BC2200DC5421 /* EditGroupViewModel.swift in Sources */, - FDEF57222C3CF03D00131302 /* (null) in Sources */, 7BA37AFB2AEB64CA002438F8 /* DisappearingMessageTimerView.swift in Sources */, FD12A8492AD63C4700EEBA0D /* SessionNavItem.swift in Sources */, - FD12A83D2AD63BCC00EEBA0D /* EditableState.swift in Sources */, FD37EA0528AA00C1003AE748 /* NotificationSettingsViewModel.swift in Sources */, C328255225CA64470062D0A7 /* ContextMenuVC+ActionView.swift in Sources */, C3548F0824456AB6009433A8 /* UIView+Wrapping.swift in Sources */, @@ -6767,7 +6898,6 @@ B886B4A92398BA1500211ABE /* QRCode.swift in Sources */, 34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */, FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */, - FDEF57262C3CF05F00131302 /* (null) in Sources */, 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, 7BA37AFD2AEF7C3D002438F8 /* VoiceMessageView_SwiftUI.swift in Sources */, 7B1B52E028580D51006069F2 /* EmojiSkinTonePicker.swift in Sources */, @@ -6811,10 +6941,10 @@ 7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */, FD71163F28E2C82C00B47552 /* SessionHeaderView.swift in Sources */, B877E24226CA12910007970A /* CallVC.swift in Sources */, - FDEF57232C3CF04300131302 /* (null) in Sources */, FDC498B92AC15FE300EDD897 /* AppNotificationAction.swift in Sources */, FD7443402D07A25C00862443 /* PushRegistrationManager.swift in Sources */, 7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */, + FDE71B0B2E79352D0023F5F9 /* DeveloperSettingsGroupsViewModel.swift in Sources */, 7B71A98F2925E2A600E54854 /* SessionFooterView.swift in Sources */, C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */, FDE754E52C9BB012002A2623 /* BezierPathView.swift in Sources */, @@ -6845,7 +6975,6 @@ 7BAF54CF27ACCEEC003D12F8 /* GlobalSearchViewController.swift in Sources */, FD37EA1728AC5605003AE748 /* NotificationContentViewModel.swift in Sources */, FD3FAB612AEA194E00DC5421 /* UserListViewModel.swift in Sources */, - B886B4A72398B23E00211ABE /* (null) in Sources */, 94CD96322E1B88C20097754D /* ExpandingAttachmentsButton.swift in Sources */, 94B3DC172AF8592200C88531 /* QuoteView_SwiftUI.swift in Sources */, 4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */, @@ -6883,15 +7012,14 @@ 45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */, 7B81FB5A2AB01B17002FB267 /* LoadingIndicatorView.swift in Sources */, 7B9F71D42852EEE2006DFE7B /* Emoji+Name.swift in Sources */, + FDE71B0D2E793B250023F5F9 /* DeveloperSettingsProViewModel.swift in Sources */, 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */, C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */, - FDEF57242C3CF04700131302 /* (null) in Sources */, 9479981C2DD44ADC008F5CD5 /* ThreadNotificationSettingsViewModel.swift in Sources */, 34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */, 9422568C2C23F8C800C0FDBF /* DisplayNameScreen.swift in Sources */, 7B9F71D72853100A006DFE7B /* Emoji+Available.swift in Sources */, FD09C5E628260FF9000CE219 /* MediaGalleryViewModel.swift in Sources */, - FDEF57212C3CF03A00131302 /* (null) in Sources */, 7B9F71D32852EEE2006DFE7B /* Emoji.swift in Sources */, C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */, 3488F9362191CC4000E524CC /* MediaView.swift in Sources */, @@ -6931,7 +7059,6 @@ 940943402C7ED62300D9D2E0 /* StartupError.swift in Sources */, FD368A6A29DE9E30000DBF1E /* UIContextualAction+Utilities.swift in Sources */, 947D7FDE2D5180F200E8E413 /* SessionNetworkScreen+ViewModel.swift in Sources */, - FDEF57252C3CF04C00131302 /* (null) in Sources */, 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */, FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */, FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */, @@ -6972,7 +7099,7 @@ FD71161528D00D6700B47552 /* ThreadDisappearingMessagesViewModelSpec.swift in Sources */, FD01503A2CA24328005B08A1 /* MockJobRunner.swift in Sources */, FD23EA5E28ED00FD0058676E /* NimbleExtensions.swift in Sources */, - FD481A9B2CB4CAF100ECC4CF /* CustomArgSummaryDescribable+SMK.swift in Sources */, + FD481A9B2CB4CAF100ECC4CF /* CustomArgSummaryDescribable+SessionMessagingKit.swift in Sources */, FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */, FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */, FDFE75B52ABD46B700655640 /* MockUserDefaults.swift in Sources */, @@ -7003,12 +7130,14 @@ FD65318D2AA025C500DFEEAA /* TestDependencies.swift in Sources */, FD49E2492B05C1D500FFBBB5 /* MockKeychain.swift in Sources */, FD99D0922D10F5EE005D2E15 /* ThreadSafeSpec.swift in Sources */, + FDD23AF02E459EDD0057E853 /* _020_AddJobUniqueHash.swift in Sources */, FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */, FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */, FD3FAB6E2AF1B28C00DC5421 /* MockFileManager.swift in Sources */, FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */, FDB11A592DD17D0600BEF49F /* MockLogger.swift in Sources */, FD8A5B302DC18D61004C689B /* GeneralCacheSpec.swift in Sources */, + FDD23AEE2E459E470057E853 /* _001_SUK_InitialSetupMigration.swift in Sources */, FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, FD0150402CA2433D005B08A1 /* BencodeDecoderSpec.swift in Sources */, FD0150412CA2433D005B08A1 /* BencodeEncoderSpec.swift in Sources */, @@ -7026,6 +7155,7 @@ FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */, FD0969FB2A6A00B100C5C365 /* Mocked.swift in Sources */, FD0150292CA23DB7005B08A1 /* GRDBExtensions.swift in Sources */, + FDD23AEF2E459EC90057E853 /* _012_AddJobPriority.swift in Sources */, FD481A942CAE0AE000ECC4CF /* MockAppContext.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -7037,6 +7167,8 @@ FDB5DB062A981C67002C8721 /* PreparedRequestSendingSpec.swift in Sources */, FD49E2482B05C1D500FFBBB5 /* MockKeychain.swift in Sources */, FDB5DB082A981F8B002C8721 /* Mocked.swift in Sources */, + FD6B92CD2E77B22D004463B5 /* SOGSMessageSpec.swift in Sources */, + FD6B92DB2E77B597004463B5 /* CryptoSOGSAPISpec.swift in Sources */, FD336F702CABB96C00C0B51B /* BatchRequestSpec.swift in Sources */, FD3765E22AD8F53B00DC1489 /* CommonSSKMockExtensions.swift in Sources */, FDB5DB142A981FAE002C8721 /* CombineExtensions.swift in Sources */, @@ -7045,11 +7177,19 @@ FD3765DF2AD8F03100DC1489 /* MockSnodeAPICache.swift in Sources */, FDB11A582DD17D0600BEF49F /* MockLogger.swift in Sources */, FDB5DB112A981FA6002C8721 /* TestExtensions.swift in Sources */, + FD6B92D22E77B270004463B5 /* SendDirectMessageRequestSpec.swift in Sources */, + FD6B92D32E77B270004463B5 /* UpdateMessageRequestSpec.swift in Sources */, FDB5DB092A981F8D002C8721 /* MockCrypto.swift in Sources */, FDAA167B2AC28E2F00DDBF77 /* SnodeRequestSpec.swift in Sources */, + FD6B92D02E77B23B004463B5 /* CapabilitiesResponse.swift in Sources */, + FD6B92D42E77B2C7004463B5 /* SOGSAPISpec.swift in Sources */, FD65318C2AA025C500DFEEAA /* TestDependencies.swift in Sources */, FDB5DB102A981FA3002C8721 /* TestConstants.swift in Sources */, FDB5DB0C2A981F96002C8721 /* MockNetwork.swift in Sources */, + FD6B92D12E77B253004463B5 /* SendSOGSMessageRequestSpec.swift in Sources */, + FD6B92D62E77B55D004463B5 /* SOGSEndpointSpec.swift in Sources */, + FD6B92D72E77B55D004463B5 /* SOGSErrorSpec.swift in Sources */, + FD6B92D82E77B55D004463B5 /* PersonalizationSpec.swift in Sources */, FD336F712CABB97800C0B51B /* DestinationSpec.swift in Sources */, FD336F722CABB97800C0B51B /* BatchResponseSpec.swift in Sources */, FD336F732CABB97800C0B51B /* HeaderSpec.swift in Sources */, @@ -7058,6 +7198,8 @@ FD336F762CABB97800C0B51B /* BencodeResponseSpec.swift in Sources */, FDB5DB0B2A981F92002C8721 /* MockGeneralCache.swift in Sources */, FD0150492CA243CB005B08A1 /* Mock.swift in Sources */, + FD6B92CE2E77B234004463B5 /* RoomPollInfoSpec.swift in Sources */, + FD6B92CF2E77B234004463B5 /* RoomSpec.swift in Sources */, FDB5DB122A981FA8002C8721 /* NimbleExtensions.swift in Sources */, FD0150282CA23DB7005B08A1 /* GRDBExtensions.swift in Sources */, FDB5DB152A981FB0002C8721 /* SynchronousStorage.swift in Sources */, @@ -7069,28 +7211,23 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FD72BDA72BE369DC00CF6CF6 /* CryptoOpenGroupAPISpec.swift in Sources */, - FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */, + FD72BDA72BE369DC00CF6CF6 /* CryptoOpenGroupSpec.swift in Sources */, FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */, - FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */, - FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */, - FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */, FDE754A82C9B964D002A2623 /* MessageReceiverGroupsSpec.swift in Sources */, FD01504C2CA243CB005B08A1 /* Mock.swift in Sources */, FD981BC92DC4641100564172 /* ExtensionHelperSpec.swift in Sources */, FD981BCB2DC4A21C00564172 /* MessageDeduplicationSpec.swift in Sources */, - FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */, + FD83B9C727CF3F10005E1583 /* CapabilitySpec.swift in Sources */, FD2AAAF128ED57B500A49611 /* SynchronousStorage.swift in Sources */, FD23CE2A2A6775660000B97C /* MockCrypto.swift in Sources */, FD336F6F2CAA37CB00C0B51B /* MockCommunityPoller.swift in Sources */, - FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */, FD481A962CAE0AE000ECC4CF /* MockAppContext.swift in Sources */, FDB11A562DD17C3300BEF49F /* MockLogger.swift in Sources */, FD0150452CA243BB005B08A1 /* LibSessionUtilSpec.swift in Sources */, FD981BCD2DC81ABF00564172 /* MockExtensionHelper.swift in Sources */, FD336F602CAA28CF00C0B51B /* CommonSMKMockExtensions.swift in Sources */, FD336F612CAA28CF00C0B51B /* MockNotificationsManager.swift in Sources */, - FD336F622CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift in Sources */, + FD336F622CAA28CF00C0B51B /* CustomArgSummaryDescribable+SessionMessagingKit.swift in Sources */, FD336F632CAA28CF00C0B51B /* MockOGMCache.swift in Sources */, FD481A922CAD17DE00ECC4CF /* LibSessionGroupMembersSpec.swift in Sources */, FD336F642CAA28CF00C0B51B /* MockCommunityPollerCache.swift in Sources */, @@ -7107,26 +7244,21 @@ FD65318B2AA025C500DFEEAA /* TestDependencies.swift in Sources */, FD49E2472B05C1D500FFBBB5 /* MockKeychain.swift in Sources */, FD3765E32AD8F56200DC1489 /* CommonSSKMockExtensions.swift in Sources */, - FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */, FD23EA6228ED0B260058676E /* CombineExtensions.swift in Sources */, FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */, FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */, FD61FCF92D308CC9005752DE /* GroupMemberSpec.swift in Sources */, - FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */, FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */, FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */, FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */, FD481A902CAD16F100ECC4CF /* LibSessionGroupInfoSpec.swift in Sources */, FDE754A92C9B964D002A2623 /* MessageSenderGroupsSpec.swift in Sources */, - FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */, FD3FAB672AF0C47000DC5421 /* DisplayPictureDownloadJobSpec.swift in Sources */, FD72BDA42BE3690B00CF6CF6 /* CryptoSMKSpec.swift in Sources */, - FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */, FD336F6C2CAA29C600C0B51B /* CommunityPollerSpec.swift in Sources */, FD481AA32CB889AE00ECC4CF /* RetrieveDefaultOpenGroupRoomsJobSpec.swift in Sources */, FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */, FD01502A2CA23DB7005B08A1 /* GRDBExtensions.swift in Sources */, - FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */, FD01503B2CA24328005B08A1 /* MockJobRunner.swift in Sources */, FD3F2EE72DE6CC4100FD6849 /* NotificationsManagerSpec.swift in Sources */, FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */, @@ -7157,7 +7289,7 @@ }; B8D64FB825BA78270029CFC0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C3C2A59E255385C100C340D1 /* SessionSnodeKit */; + target = C3C2A59E255385C100C340D1 /* SessionNetworkingKit */; targetProxy = B8D64FB725BA78270029CFC0 /* PBXContainerItemProxy */; }; B8D64FBA25BA78270029CFC0 /* PBXTargetDependency */ = { @@ -7172,7 +7304,7 @@ }; B8D64FC425BA784A0029CFC0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C3C2A59E255385C100C340D1 /* SessionSnodeKit */; + target = C3C2A59E255385C100C340D1 /* SessionNetworkingKit */; targetProxy = B8D64FC325BA784A0029CFC0 /* PBXContainerItemProxy */; }; B8D64FC625BA784A0029CFC0 /* PBXTargetDependency */ = { @@ -7192,7 +7324,7 @@ }; C3C2A5A5255385C100C340D1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C3C2A59E255385C100C340D1 /* SessionSnodeKit */; + target = C3C2A59E255385C100C340D1 /* SessionNetworkingKit */; targetProxy = C3C2A5A4255385C100C340D1 /* PBXContainerItemProxy */; }; C3C2A67F255388CC00C340D1 /* PBXTargetDependency */ = { @@ -7254,7 +7386,7 @@ FDB5DB002A981C43002C8721 /* PBXTargetDependency */ = { isa = PBXTargetDependency; platformFilter = ios; - target = C3C2A59E255385C100C340D1 /* SessionSnodeKit */; + target = C3C2A59E255385C100C340D1 /* SessionNetworkingKit */; targetProxy = FDB5DAFF2A981C43002C8721 /* PBXContainerItemProxy */; }; FDC4389427B9FFC700C60D73 /* PBXTargetDependency */ = { @@ -7809,7 +7941,7 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist; + INFOPLIST_FILE = SessionNetworkingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -7820,7 +7952,7 @@ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionSnodeKit"; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionNetworkingKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; @@ -7882,7 +8014,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist; + INFOPLIST_FILE = SessionNetworkingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -7893,7 +8025,7 @@ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionSnodeKit"; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionNetworkingKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -8188,7 +8320,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COMPILE_LIB_SESSION = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 626; + CURRENT_PROJECT_VERSION = 633; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -8228,7 +8360,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.6; LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util"; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; - MARKETING_VERSION = 2.14.2; + MARKETING_VERSION = 2.14.3; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-Werror=protocol"; OTHER_SWIFT_FLAGS = "-D DEBUG -Xfrontend -warn-long-expression-type-checking=100"; @@ -8269,7 +8401,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; COMPILE_LIB_SESSION = ""; - CURRENT_PROJECT_VERSION = 626; + CURRENT_PROJECT_VERSION = 633; ENABLE_BITCODE = NO; ENABLE_MODULE_VERIFIER = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -8304,7 +8436,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.6; LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util"; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; - MARKETING_VERSION = 2.14.2; + MARKETING_VERSION = 2.14.3; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "-DNS_BLOCK_ASSERTIONS=1", @@ -8418,8 +8550,8 @@ ENABLE_MODULE_VERIFIER = NO; GCC_DYNAMIC_NO_PIC = NO; GENERATE_INFOPLIST_FILE = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests; - PRODUCT_NAME = SessionSnodeKitTests; + PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionNetworkingKitTests; + PRODUCT_NAME = SessionNetworkingKitTests; }; name = Debug; }; @@ -8428,8 +8560,8 @@ buildSettings = { ENABLE_MODULE_VERIFIER = NO; GENERATE_INFOPLIST_FILE = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests; - PRODUCT_NAME = SessionSnodeKitTests; + PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionNetworkingKitTests; + PRODUCT_NAME = SessionNetworkingKitTests; }; name = App_Store_Release; }; @@ -8617,8 +8749,8 @@ isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests; - PRODUCT_NAME = SessionSnodeKitTests; + PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionNetworkingKitTests; + PRODUCT_NAME = SessionNetworkingKitTests; }; name = Debug_Compile_LibSession; }; @@ -8626,8 +8758,8 @@ isa = XCBuildConfiguration; buildSettings = { GENERATE_INFOPLIST_FILE = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionSnodeKitTests; - PRODUCT_NAME = SessionSnodeKitTests; + PRODUCT_BUNDLE_IDENTIFIER = io.oxen.SessionNetworkingKitTests; + PRODUCT_NAME = SessionNetworkingKitTests; }; name = App_Store_Release_Compile_LibSession; }; @@ -8750,7 +8882,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COMPILE_LIB_SESSION = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 626; + CURRENT_PROJECT_VERSION = 633; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -8789,7 +8921,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.6; LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util"; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; - MARKETING_VERSION = 2.14.2; + MARKETING_VERSION = 2.14.3; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "-fobjc-arc-exceptions", @@ -9139,7 +9271,7 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist; + INFOPLIST_FILE = SessionNetworkingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -9150,7 +9282,7 @@ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionSnodeKit"; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionNetworkingKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; @@ -9337,7 +9469,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; COMPILE_LIB_SESSION = YES; - CURRENT_PROJECT_VERSION = 626; + CURRENT_PROJECT_VERSION = 633; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -9370,7 +9502,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.6; LIB_SESSION_SOURCE_DIR = "${SRCROOT}/../LibSession-Util"; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; - MARKETING_VERSION = 2.14.2; + MARKETING_VERSION = 2.14.3; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "-DNS_BLOCK_ASSERTIONS=1", @@ -9858,7 +9990,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist; + INFOPLIST_FILE = SessionNetworkingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -9869,7 +10001,7 @@ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionSnodeKit"; + PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.SessionNetworkingKit"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -10175,7 +10307,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = App_Store_Release; }; - C3C2A5AA255385C100C340D1 /* Build configuration list for PBXNativeTarget "SessionSnodeKit" */ = { + C3C2A5AA255385C100C340D1 /* Build configuration list for PBXNativeTarget "SessionNetworkingKit" */ = { isa = XCConfigurationList; buildConfigurations = ( C3C2A5A8255385C100C340D1 /* Debug */, @@ -10230,7 +10362,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = App_Store_Release; }; - FD2272522C32910F004D8A6C /* Build configuration list for PBXNativeTarget "SessionSnodeKitTests" */ = { + FD2272522C32910F004D8A6C /* Build configuration list for PBXNativeTarget "SessionNetworkingKitTests" */ = { isa = XCConfigurationList; buildConfigurations = ( FD2272502C32910F004D8A6C /* Debug */, diff --git a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme index ad1576e055..e20af5e4f5 100644 --- a/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme +++ b/Session.xcodeproj/xcshareddata/xcschemes/Session.xcscheme @@ -77,8 +77,8 @@ @@ -55,8 +55,8 @@ diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index d747fbcdfd..f08656626c 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -9,7 +9,7 @@ import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { private let dependencies: Dependencies diff --git a/Session/Calls/WebRTC/WebRTCSession.swift b/Session/Calls/WebRTC/WebRTCSession.swift index 99e48d1e25..d579d8a1a5 100644 --- a/Session/Calls/WebRTC/WebRTCSession.swift +++ b/Session/Calls/WebRTC/WebRTCSession.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import WebRTC -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Closed Groups/EditGroupViewModel.swift b/Session/Closed Groups/EditGroupViewModel.swift index 01916f533c..a0dd9dba89 100644 --- a/Session/Closed Groups/EditGroupViewModel.swift +++ b/Session/Closed Groups/EditGroupViewModel.swift @@ -5,15 +5,14 @@ import Combine import GRDB import DifferenceKit import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit -class EditGroupViewModel: SessionTableViewModel, NavigatableStateHolder, EditableStateHolder, ObservableTableSource { +class EditGroupViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { public let dependencies: Dependencies public let navigatableState: NavigatableState = NavigatableState() - public let editableState: EditableState = EditableState() public let state: TableDataState = TableDataState() public let observableState: ObservableTableSourceState = ObservableTableSourceState() private let selectedIdsSubject: CurrentValueSubject<(name: String, ids: Set), Never> = CurrentValueSubject(("", [])) @@ -534,7 +533,7 @@ class EditGroupViewModel: SessionTableViewModel, NavigatableStateHolder, Editabl case (.some(let inviteByIdValue), _): // This could be an ONS name let viewController = ModalActivityIndicatorViewController() { modalActivityIndicator in - SnodeAPI + Network.SnodeAPI .getSessionID(for: inviteByIdValue, using: dependencies) .subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies) .receive(on: DispatchQueue.main, using: dependencies) diff --git a/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift b/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift index 52dfc7a33f..8ae261fc79 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift @@ -3,7 +3,7 @@ import UIKit import SessionUIKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit extension ContextMenuVC { final class ActionView: UIView { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 1330cba707..ad0541a19d 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -14,7 +14,7 @@ import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit import SwiftUI -import SessionSnodeKit +import SessionNetworkingKit extension ConversationVC: InputViewDelegate, @@ -22,7 +22,8 @@ extension ConversationVC: ContextMenuActionDelegate, SendMediaNavDelegate, AttachmentApprovalViewControllerDelegate, - GifPickerViewControllerDelegate + GifPickerViewControllerDelegate, + UIGestureRecognizerDelegate { // MARK: - Open Settings @@ -33,6 +34,11 @@ extension ConversationVC: openSettingsFromTitleView() } + // Handle taps outside of tableview cell to dismiss keyboard + @MainActor @objc func dismissKeyboardOnTap() { + _ = self.snInputView.resignFirstResponder() + } + @MainActor func openSettingsFromTitleView() { // If we shouldn't be able to access settings then disable the title view shortcuts guard viewModel.threadData.canAccessSettings(using: viewModel.dependencies) else { return } @@ -254,6 +260,11 @@ extension ConversationVC: return true } + + // MARK: - UIGestureRecognizerDelegate + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } // MARK: - SendMediaNavDelegate @@ -860,6 +871,9 @@ extension ConversationVC: } func showLinkPreviewSuggestionModal() { + // Hides accessory view while link preview confirmation is presented + hideInputAccessoryView() + let linkPreviewModal: ConfirmationModal = ConfirmationModal( info: ConfirmationModal.Info( title: "linkPreviewsEnable".localized(), @@ -870,12 +884,17 @@ extension ConversationVC: ), confirmTitle: "enable".localized(), confirmStyle: .danger, - cancelStyle: .alert_text - ) { [weak self, dependencies = viewModel.dependencies] _ in - dependencies.setAsync(.areLinkPreviewsEnabled, true) { - self?.snInputView.autoGenerateLinkPreview() + cancelStyle: .alert_text, + onConfirm: { [weak self, dependencies = viewModel.dependencies] _ in + dependencies.setAsync(.areLinkPreviewsEnabled, true) { + self?.snInputView.autoGenerateLinkPreview() + } + }, + afterClosed: { [weak self] in + // Bring back accessory view after confirmation action + self?.showInputAccessoryView() } - } + ) ) present(linkPreviewModal, animated: true, completion: nil) @@ -1055,7 +1074,7 @@ extension ConversationVC: } // MARK: MessageCellDelegate - + func handleItemLongPressed(_ cellViewModel: MessageViewModel) { // Show the unblock modal if needed guard self.viewModel.threadData.threadIsBlocked != true else { @@ -1746,7 +1765,7 @@ extension ConversationVC: let openGroupServerMessageId: Int64 = cellViewModel.openGroupServerMessageId else { return } - let pendingChange: OpenGroupAPI.PendingChange = viewModel.dependencies[singleton: .openGroupManager] + let pendingChange: OpenGroupManager.PendingChange = viewModel.dependencies[singleton: .openGroupManager] .addPendingReaction( emoji: emoji, id: openGroupServerMessageId, @@ -1756,7 +1775,7 @@ extension ConversationVC: ) Result { - try OpenGroupAPI.preparedReactionDeleteAll( + try Network.SOGS.preparedReactionDeleteAll( emoji: emoji, id: openGroupServerMessageId, roomToken: roomToken, @@ -1831,14 +1850,14 @@ extension ConversationVC: typealias OpenGroupInfo = ( pendingReaction: Reaction?, - pendingChange: OpenGroupAPI.PendingChange, + pendingChange: OpenGroupManager.PendingChange, preparedRequest: Network.PreparedRequest ) /// Perform the sending logic, we generate the pending reaction first in a deferred future closure to prevent the OpenGroup /// cache from blocking either the main thread or the database write thread Deferred { [dependencies = viewModel.dependencies] in - Future { resolver in + Future { resolver in guard threadVariant == .community, let serverMessageId: Int64 = cellViewModel.openGroupServerMessageId, @@ -1859,7 +1878,7 @@ extension ConversationVC: } } .subscribe(on: DispatchQueue.global(qos: .userInitiated), using: viewModel.dependencies) - .flatMapStorageWritePublisher(using: viewModel.dependencies) { [weak self, dependencies = viewModel.dependencies] db, pendingChange -> (OpenGroupAPI.PendingChange?, Reaction?, Message.Destination, AuthenticationMethod) in + .flatMapStorageWritePublisher(using: viewModel.dependencies) { [weak self, dependencies = viewModel.dependencies] db, pendingChange -> (OpenGroupManager.PendingChange?, Reaction?, Message.Destination, AuthenticationMethod) in // Update the thread to be visible (if it isn't already) if self?.viewModel.threadData.threadShouldBeVisible == false { try SessionThread.updateVisibility( @@ -1937,12 +1956,12 @@ extension ConversationVC: let serverMessageId: Int64 = cellViewModel.openGroupServerMessageId, let openGroupServer: String = cellViewModel.threadOpenGroupServer, let openGroupRoom: String = openGroupRoom, - let pendingChange: OpenGroupAPI.PendingChange = pendingChange + let pendingChange: OpenGroupManager.PendingChange = pendingChange else { throw MessageSenderError.invalidMessage } let preparedRequest: Network.PreparedRequest = try { guard !remove else { - return try OpenGroupAPI + return try Network.SOGS .preparedReactionDelete( emoji: emoji, id: serverMessageId, @@ -1953,7 +1972,7 @@ extension ConversationVC: .map { _, response in response.seqNo } } - return try OpenGroupAPI + return try Network.SOGS .preparedReactionAdd( emoji: emoji, id: serverMessageId, @@ -2250,10 +2269,26 @@ extension ConversationVC: isOutgoing: (cellViewModel.variant == .standardOutgoing) ) - if isShowingSearchUI { willManuallyCancelSearchUI() } + // If the `MessageInfoViewController` is visible then we want to show the keyboard after + // the pop transition completes (and don't want to delay triggering the completion closure) + let messageInfoScreenVisible: Bool = (self.navigationController?.viewControllers.last is MessageInfoViewController) + + guard !messageInfoScreenVisible else { + if self.isShowingSearchUI == true { self.willManuallyCancelSearchUI() } + self.hasPendingInputKeyboardPresentationEvent = true + completion?() + return + } - _ = snInputView.becomeFirstResponder() - completion?() + // Add delay before doing any ui updates + // Delay added to give time for long press actions to dismiss + let delay = completion == nil ? 0 : ContextMenuVC.dismissDuration + + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in + if self?.isShowingSearchUI == true { self?.willManuallyCancelSearchUI() } + _ = self?.snInputView.becomeFirstResponder() + completion?() + } } func copy(_ cellViewModel: MessageViewModel, completion: (() -> Void)?) { @@ -2579,7 +2614,7 @@ extension ConversationVC: } .publisher .tryFlatMap { (roomToken: String, authMethod: AuthenticationMethod) in - try OpenGroupAPI.preparedUserBan( + try Network.SOGS.preparedUserBan( sessionId: cellViewModel.authorId, from: [roomToken], authMethod: authMethod, @@ -2657,7 +2692,7 @@ extension ConversationVC: } .publisher .tryFlatMap { (roomToken: String, authMethod: AuthenticationMethod) in - try OpenGroupAPI.preparedUserBanAndDeleteAllMessages( + try Network.SOGS.preparedUserBanAndDeleteAllMessages( sessionId: cellViewModel.authorId, roomToken: roomToken, authMethod: authMethod, diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index ab8471b452..0b13d35bbd 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -29,6 +29,10 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa /// never have disappeared before - this is only needed for value observers since they run asynchronously) private var hasReloadedThreadDataAfterDisappearance: Bool = true + /// This flag indicates that a need for inputview keyboard presentation is needed, this is in events + /// where a delegate action is trigger before poping back into `ConversationVC` + var hasPendingInputKeyboardPresentationEvent: Bool = false + var focusedInteractionInfo: Interaction.TimestampInfo? var focusBehaviour: ConversationViewModel.FocusBehaviour = .none @@ -382,6 +386,16 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa return result }() + + // Handle taps outside of tableview cell + private lazy var tableViewTapGesture: UITapGestureRecognizer = { + let result: UITapGestureRecognizer = UITapGestureRecognizer() + result.delegate = self + result.addTarget(self, action: #selector(dismissKeyboardOnTap)) + result.cancelsTouchesInView = false + + return result + }() // MARK: - Settings @@ -533,6 +547,9 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa object: nil ) } + + // Gesture + view.addGestureRecognizer(tableViewTapGesture) self.viewModel.navigatableState.setupBindings(viewController: self, disposables: &self.viewModel.disposables) @@ -581,6 +598,15 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa self?.didFinishInitialLayout = true self?.viewIsAppearing = false self?.lastPresentedViewController = nil + + // Show inputview keyboard + if self?.hasPendingInputKeyboardPresentationEvent == true { + // Added 0.1 delay to remove inputview stutter animation glitch while keyboard is animating up + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + _ = self?.snInputView.becomeFirstResponder() + } + self?.hasPendingInputKeyboardPresentationEvent = false + } } } @@ -1567,7 +1593,8 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa // value will break things) let tableViewBottom: CGFloat = (tableView.contentSize.height - tableView.bounds.height + tableView.contentInset.bottom) - if tableView.contentOffset.y < (tableViewBottom - 5) { + // Added `insetDifference > 0` to remove sudden table collapse and overscroll + if tableView.contentOffset.y < (tableViewBottom - 5) && insetDifference > 0 { tableView.contentOffset.y += insetDifference } diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index 07c82e7e4b..804521bd4d 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -6,7 +6,7 @@ import UniformTypeIdentifiers import Lucide import GRDB import DifferenceKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit import SessionUIKit diff --git a/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift b/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift index b366b5e2a8..289a6bb8e2 100644 --- a/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift +++ b/Session/Conversations/Emoji Picker/EmojiPickerCollectionView.swift @@ -322,7 +322,12 @@ private class EmojiSectionHeader: UICollectionReusableView { label.font = .systemFont(ofSize: Values.smallFontSize) label.themeTextColor = .textPrimary addSubview(label) - label.pin(to: self) + + label.pin(.top, to: .top, of: self) + label.pin(.leading, to: .leading, of: self, withInset: Values.mediumSpacing) + label.pin(.trailing, to: .trailing, of: self, withInset: -Values.mediumSpacing) + label.pin(.bottom, to: .bottom, of: self) + label.setCompressionResistance(to: .required) } diff --git a/Session/Conversations/Input View/InputView.swift b/Session/Conversations/Input View/InputView.swift index 74316f9525..b75531a7e0 100644 --- a/Session/Conversations/Input View/InputView.swift +++ b/Session/Conversations/Input View/InputView.swift @@ -62,6 +62,15 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M return result }() + private lazy var swipeGestureRecognizer: UISwipeGestureRecognizer = { + let result: UISwipeGestureRecognizer = UISwipeGestureRecognizer() + result.direction = .down + result.addTarget(self, action: #selector(didSwipeDown)) + result.cancelsTouchesInView = false + + return result + }() + private var bottomStackView: UIStackView? private lazy var attachmentsButton: ExpandingAttachmentsButton = { let result = ExpandingAttachmentsButton(delegate: delegate) @@ -227,6 +236,7 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M autoresizingMask = .flexibleHeight addGestureRecognizer(tapGestureRecognizer) + addGestureRecognizer(swipeGestureRecognizer) // Background & blur let backgroundView = UIView() @@ -454,6 +464,7 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M self.accessibilityIdentifier = updatedInputState.accessibility?.identifier self.accessibilityLabel = updatedInputState.accessibility?.label tapGestureRecognizer.isEnabled = (updatedInputState.allowedInputTypes == .none) + inputState = updatedInputState disabledInputLabel.text = (updatedInputState.message ?? "") disabledInputLabel.accessibilityIdentifier = updatedInputState.messageAccessibility?.identifier @@ -630,6 +641,10 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M @objc private func characterLimitLabelTapped() { delegate?.handleCharacterLimitLabelTapped() } + + @objc private func didSwipeDown() { + inputTextView.resignFirstResponder() + } // MARK: - Convenience diff --git a/Session/Conversations/Message Cells/CallMessageCell.swift b/Session/Conversations/Message Cells/CallMessageCell.swift index 305ea3a29c..47dca8d33b 100644 --- a/Session/Conversations/Message Cells/CallMessageCell.swift +++ b/Session/Conversations/Message Cells/CallMessageCell.swift @@ -10,12 +10,21 @@ final class CallMessageCell: MessageCell { private static let iconSize: CGFloat = 16 private static let timerViewSize: CGFloat = 16 private static let inset = Values.mediumSpacing + private static let verticalInset = Values.smallSpacing + private static let horizontalInset = Values.mediumSmallSpacing private static let margin = UIScreen.main.bounds.width * 0.1 private var isHandlingLongPress: Bool = false override var contextSnapshotView: UIView? { return container } + override var allowedGestureRecognizers: Set { + return [ + .longPress, + .tap + ] + } + // MARK: - UI private lazy var topConstraint: NSLayoutConstraint = mainStackView.pin(.top, to: .top, of: self, withInset: CallMessageCell.inset) @@ -48,7 +57,7 @@ final class CallMessageCell: MessageCell { private lazy var label: UILabel = { let result: UILabel = UILabel() - result.font = .boldSystemFont(ofSize: Values.smallFontSize) + result.font = .systemFont(ofSize: Values.mediumFontSize) result.themeTextColor = .textPrimary result.textAlignment = .center result.lineBreakMode = .byWordWrapping @@ -63,28 +72,28 @@ final class CallMessageCell: MessageCell { result.layer.cornerRadius = 18 result.addSubview(label) - label.pin(.top, to: .top, of: result, withInset: CallMessageCell.inset) + label.pin(.top, to: .top, of: result, withInset: CallMessageCell.verticalInset) label.pin( .left, to: .left, of: result, - withInset: ((CallMessageCell.inset * 2) + infoImageView.bounds.size.width) + withInset: ((CallMessageCell.horizontalInset * 2) + infoImageView.bounds.size.width) ) label.pin( .right, to: .right, of: result, - withInset: -((CallMessageCell.inset * 2) + infoImageView.bounds.size.width) + withInset: -((CallMessageCell.horizontalInset * 2) + infoImageView.bounds.size.width) ) - label.pin(.bottom, to: .bottom, of: result, withInset: -CallMessageCell.inset) + label.pin(.bottom, to: .bottom, of: result, withInset: -CallMessageCell.verticalInset) result.addSubview(iconImageView) iconImageView.center(.vertical, in: result) - iconImageView.pin(.left, to: .left, of: result, withInset: CallMessageCell.inset) + iconImageView.pin(.left, to: .left, of: result, withInset: CallMessageCell.horizontalInset) result.addSubview(infoImageView) infoImageView.center(.vertical, in: result) - infoImageView.pin(.right, to: .right, of: result, withInset: -CallMessageCell.inset) + infoImageView.pin(.right, to: .right, of: result, withInset: -CallMessageCell.horizontalInset) return result }() @@ -113,15 +122,6 @@ final class CallMessageCell: MessageCell { mainStackView.pin(.bottom, to: .bottom, of: self, withInset: -CallMessageCell.inset) } - override func setUpGestureRecognizers() { - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - addGestureRecognizer(longPressRecognizer) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - tapGestureRecognizer.numberOfTapsRequired = 1 - addGestureRecognizer(tapGestureRecognizer) - } - // MARK: - Updating override func update( @@ -205,7 +205,7 @@ final class CallMessageCell: MessageCell { // MARK: - Interaction - @objc func handleLongPress(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { if [ .ended, .cancelled, .failed ].contains(gestureRecognizer.state) { isHandlingLongPress = false return @@ -216,7 +216,7 @@ final class CallMessageCell: MessageCell { isHandlingLongPress = true } - @objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { guard let dependencies: Dependencies = self.dependencies, let cellViewModel: MessageViewModel = self.viewModel, diff --git a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift index 566ddceca1..baed864ba4 100644 --- a/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift +++ b/Session/Conversations/Message Cells/Content Views/DeletedMessageView.swift @@ -10,6 +10,8 @@ import SessionUtilitiesKit final class DeletedMessageView: UIView { private static let iconSize: CGFloat = 18 private static let iconImageViewSize: CGFloat = 30 + private static let horizontalInset = Values.mediumSmallSpacing + private static let verticalInset = Values.smallSpacing // MARK: - Lifecycle @@ -29,24 +31,20 @@ final class DeletedMessageView: UIView { } private func setUpViewHierarchy(textColor: ThemeValue, variant: Interaction.Variant, maxWidth: CGFloat) { - // Image view - let imageContainerView: UIView = UIView() - imageContainerView.set(.width, to: DeletedMessageView.iconImageViewSize) - imageContainerView.set(.height, to: DeletedMessageView.iconImageViewSize) + let trashIcon = Lucide.image(icon: .trash2, size: DeletedMessageView.iconSize)? + .withRenderingMode(.alwaysTemplate) - let imageView = UIImageView(image: Lucide.image(icon: .trash2, size: DeletedMessageView.iconSize)?.withRenderingMode(.alwaysTemplate)) + let imageView = UIImageView(image: trashIcon) imageView.themeTintColor = textColor + imageView.alpha = Values.highOpacity imageView.contentMode = .scaleAspectFit imageView.set(.width, to: DeletedMessageView.iconSize) imageView.set(.height, to: DeletedMessageView.iconSize) - imageContainerView.addSubview(imageView) - imageView.center(in: imageContainerView) // Body label let titleLabel = UILabel() titleLabel.setContentHuggingPriority(.required, for: .vertical) - titleLabel.preferredMaxLayoutWidth = maxWidth - 6 // `6` for the `stackView.layoutMargins` - titleLabel.font = .systemFont(ofSize: Values.smallFontSize) + titleLabel.font = .italicSystemFont(ofSize: Values.mediumFontSize) titleLabel.text = { switch variant { case .standardIncomingDeletedLocally, .standardOutgoingDeletedLocally: @@ -56,19 +54,29 @@ final class DeletedMessageView: UIView { } }() titleLabel.themeTextColor = textColor + titleLabel.alpha = Values.highOpacity titleLabel.lineBreakMode = .byTruncatingTail titleLabel.numberOfLines = 2 // Stack view - let stackView = UIStackView(arrangedSubviews: [ imageContainerView, titleLabel ]) + let stackView = UIStackView(arrangedSubviews: [ + imageView, + titleLabel + ]) stackView.axis = .horizontal stackView.alignment = .center + stackView.spacing = Values.smallSpacing stackView.isLayoutMarginsRelativeArrangement = true stackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 6) addSubview(stackView) let calculatedSize: CGSize = stackView.systemLayoutSizeFitting(CGSize(width: maxWidth, height: 999)) - stackView.pin(to: self, withInset: Values.smallSpacing) - stackView.set(.height, to: calculatedSize.height) + + stackView.pin(.top, to: .top, of: self, withInset: Self.verticalInset) + stackView.pin(.leading, to: .leading, of: self, withInset: Self.horizontalInset) + stackView.pin(.trailing, to: .trailing, of: self, withInset: -Self.horizontalInset) + stackView.pin(.bottom, to: .bottom, of: self, withInset: -Self.verticalInset) + + stackView.set(.height, greaterThanOrEqualTo: calculatedSize.height) } } diff --git a/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift b/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift index b0f17cc31e..b835fe5e86 100644 --- a/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift +++ b/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift @@ -1,7 +1,7 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import UIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit class DisappearingMessageTimerView: UIView { diff --git a/Session/Conversations/Message Cells/Content Views/MediaView.swift b/Session/Conversations/Message Cells/Content Views/MediaView.swift index 01a44703a9..09e9b1e7b0 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaView.swift @@ -198,7 +198,7 @@ public class MediaView: UIView { guard processedData == nil else { return } Log.error("[MediaView] Could not load thumbnail") - Task { @MainActor [weak self] in self?.configure(forError: .invalid) } + self?.configure(forError: .invalid) } } diff --git a/Session/Conversations/Message Cells/InfoMessageCell.swift b/Session/Conversations/Message Cells/InfoMessageCell.swift index 9c626a90c5..a5196a0b88 100644 --- a/Session/Conversations/Message Cells/InfoMessageCell.swift +++ b/Session/Conversations/Message Cells/InfoMessageCell.swift @@ -13,6 +13,13 @@ final class InfoMessageCell: MessageCell { override var contextSnapshotView: UIView? { return label } + override var allowedGestureRecognizers: Set { + return [ + .longPress, + .tap + ] + } + // MARK: - UI private lazy var iconContainerViewWidthConstraint = iconContainerView.set(.width, to: InfoMessageCell.iconSize) @@ -77,15 +84,6 @@ final class InfoMessageCell: MessageCell { stackView.pin(.right, to: .right, of: self, withInset: -Values.massiveSpacing) stackView.pin(.bottom, to: .bottom, of: self, withInset: -InfoMessageCell.inset) } - - override func setUpGestureRecognizers() { - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - addGestureRecognizer(longPressRecognizer) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - tapGestureRecognizer.numberOfTapsRequired = 1 - addGestureRecognizer(tapGestureRecognizer) - } // MARK: - Updating @@ -169,7 +167,7 @@ final class InfoMessageCell: MessageCell { // MARK: - Interaction - @objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { + override func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { if [ .ended, .cancelled, .failed ].contains(gestureRecognizer.state) { isHandlingLongPress = false return @@ -180,9 +178,9 @@ final class InfoMessageCell: MessageCell { isHandlingLongPress = true } - @objc func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { guard let cellViewModel: MessageViewModel = self.viewModel else { return } - + if cellViewModel.variant == .infoDisappearingMessagesUpdate && cellViewModel.canDoFollowingSetting() { delegate?.handleItemTapped(cellViewModel, cell: self, cellLocation: gestureRecognizer.location(in: self)) } diff --git a/Session/Conversations/Message Cells/MessageCell.swift b/Session/Conversations/Message Cells/MessageCell.swift index 39730a9d6c..ec40f8e72e 100644 --- a/Session/Conversations/Message Cells/MessageCell.swift +++ b/Session/Conversations/Message Cells/MessageCell.swift @@ -10,11 +10,16 @@ public enum SwipeState { case cancelled } +public enum GestureRecognizerType { + case tap, longPress, doubleTap +} + public class MessageCell: UITableViewCell { var dependencies: Dependencies? var viewModel: MessageViewModel? weak var delegate: MessageCellDelegate? open var contextSnapshotView: UIView? { return nil } + open var allowedGestureRecognizers: Set { return [] } // Override to have gestures // MARK: - Lifecycle @@ -41,7 +46,32 @@ public class MessageCell: UITableViewCell { } func setUpGestureRecognizers() { - // To be overridden by subclasses + var tapGestureRecognizer: UITapGestureRecognizer? + var doubleTapGestureRecognizer: UITapGestureRecognizer? + + if allowedGestureRecognizers.contains(.tap) { + let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) + tapGesture.numberOfTapsRequired = 1 + addGestureRecognizer(tapGesture) + tapGestureRecognizer = tapGesture + } + + if allowedGestureRecognizers.contains(.doubleTap) { + let doubleTapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap)) + doubleTapGesture.numberOfTapsRequired = 2 + addGestureRecognizer(doubleTapGesture) + doubleTapGestureRecognizer = doubleTapGesture + } + + if allowedGestureRecognizers.contains(.longPress) { + let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) + addGestureRecognizer(longPressGesture) + } + + // If we have both tap and double tap gestures then the single tap should fail if a double tap occurs + if let tapGesture: UITapGestureRecognizer = tapGestureRecognizer, let doubleTapGesture: UITapGestureRecognizer = doubleTapGestureRecognizer { + tapGesture.require(toFail: doubleTapGesture) + } } // MARK: - Updating @@ -93,6 +123,16 @@ public class MessageCell: UITableViewCell { return CallMessageCell.self } } + + // MARK: - Gesture events + @objc + func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {} + + @objc + func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {} + + @objc + func handleDoubleTap() {} } // MARK: - MessageCellDelegate diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 21a9517955..0dbde5e460 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -25,6 +25,14 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { override var contextSnapshotView: UIView? { return snContentView } + override var allowedGestureRecognizers: Set { + return [ + .tap, + .longPress, + .doubleTap + ] + } + // Constraints internal lazy var authorLabelTopConstraint = authorLabel.pin(.top, to: .top, of: self) private lazy var authorLabelHeightConstraint = authorLabel.set(.height, to: 0) @@ -270,21 +278,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { messageStatusLabelPaddingView.pin(.leading, to: .leading, of: messageStatusContainerView) messageStatusLabelPaddingView.pin(.trailing, to: .trailing, of: messageStatusContainerView) } - - override func setUpGestureRecognizers() { - let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) - addGestureRecognizer(longPressRecognizer) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - tapGestureRecognizer.numberOfTapsRequired = 1 - addGestureRecognizer(tapGestureRecognizer) - - let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap)) - doubleTapGestureRecognizer.numberOfTapsRequired = 2 - addGestureRecognizer(doubleTapGestureRecognizer) - tapGestureRecognizer.require(toFail: doubleTapGestureRecognizer) - } - + // MARK: - Updating override func update( @@ -968,7 +962,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { } } - @objc func handleLongPress(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { if [ .ended, .cancelled, .failed ].contains(gestureRecognizer.state) { isHandlingLongPress = false return @@ -994,9 +988,9 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { isHandlingLongPress = true } - @objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { + override func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { guard let cellViewModel: MessageViewModel = self.viewModel else { return } - + let location = gestureRecognizer.location(in: self) if profilePictureView.bounds.contains(profilePictureView.convert(location, from: self)), cellViewModel.shouldShowProfile { @@ -1062,7 +1056,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { } } - @objc private func handleDoubleTap() { + override func handleDoubleTap() { guard let cellViewModel: MessageViewModel = self.viewModel else { return } delegate?.handleItemDoubleTapped(cellViewModel) diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift index 1f780be902..ff45656e18 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift @@ -7,7 +7,7 @@ import DifferenceKit import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { typealias TableItem = String diff --git a/Session/Conversations/Settings/ThreadNotificationSettingsViewModel.swift b/Session/Conversations/Settings/ThreadNotificationSettingsViewModel.swift index e89a98303b..f5b51cf455 100644 --- a/Session/Conversations/Settings/ThreadNotificationSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadNotificationSettingsViewModel.swift @@ -7,7 +7,7 @@ import DifferenceKit import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit class ThreadNotificationSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { public let dependencies: Dependencies diff --git a/Session/Conversations/Settings/ThreadSettingsViewModel.swift b/Session/Conversations/Settings/ThreadSettingsViewModel.swift index b5c39cc9ce..8ee7a40590 100644 --- a/Session/Conversations/Settings/ThreadSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadSettingsViewModel.swift @@ -9,7 +9,7 @@ import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit class ThreadSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { public let dependencies: Dependencies diff --git a/Session/Emoji/Emoji+Category.swift b/Session/Emoji/Emoji+Category.swift index 343d9a0e87..996f7d83a4 100644 --- a/Session/Emoji/Emoji+Category.swift +++ b/Session/Emoji/Emoji+Category.swift @@ -20,21 +20,29 @@ extension Emoji { var localizedName: String { switch self { case .smileysAndPeople: - return NSLocalizedString("emojiCategorySmileys", comment: "The name for the emoji category 'Smileys & People'") + // The name for the emoji category 'Smileys & People' + return "emojiCategorySmileys".localized() case .animals: - return NSLocalizedString("emojiCategoryAnimals", comment: "The name for the emoji category 'Animals & Nature'") + // The name for the emoji category 'Animals & Nature' + return "emojiCategoryAnimals".localized() case .food: - return NSLocalizedString("emojiCategoryFood", comment: "The name for the emoji category 'Food & Drink'") + // The name for the emoji category 'Food & Drink' + return "emojiCategoryFood".localized() case .activities: - return NSLocalizedString("emojiCategoryActivities", comment: "The name for the emoji category 'Activities'") + // The name for the emoji category 'Activities' + return "emojiCategoryActivities".localized() case .travel: - return NSLocalizedString("emojiCategoryTravel", comment: "The name for the emoji category 'Travel & Places'") + // The name for the emoji category 'Travel & Places' + return "emojiCategoryTravel".localized() case .objects: - return NSLocalizedString("emojiCategoryObjects", comment: "The name for the emoji category 'Objects'") + // The name for the emoji category 'Objects' + return "emojiCategoryObjects".localized() case .symbols: - return NSLocalizedString("emojiCategorySymbols", comment: "The name for the emoji category 'Symbols'") + // The name for the emoji category 'Symbols' + return "emojiCategorySymbols".localized() case .flags: - return NSLocalizedString("emojiCategoryFlags", comment: "The name for the emoji category 'Flags'") + // The name for the emoji category 'Flags' + return "emojiCategoryFlags".localized() } } @@ -92,6 +100,8 @@ extension Emoji { .faceExhaling, .lyingFace, .shakingFace, + .headShakingHorizontally, + .headShakingVertically, .relieved, .pensive, .sleepy, @@ -450,24 +460,42 @@ extension Emoji { .walking, .manWalking, .womanWalking, + .personWalkingFacingRight, + .womanWalkingFacingRight, + .manWalkingFacingRight, .standingPerson, .manStanding, .womanStanding, .kneelingPerson, .manKneeling, .womanKneeling, + .personKneelingFacingRight, + .womanKneelingFacingRight, + .manKneelingFacingRight, .personWithProbingCane, + .personWithWhiteCaneFacingRight, .manWithProbingCane, + .manWithWhiteCaneFacingRight, .womanWithProbingCane, + .womanWithWhiteCaneFacingRight, .personInMotorizedWheelchair, + .personInMotorizedWheelchairFacingRight, .manInMotorizedWheelchair, + .manInMotorizedWheelchairFacingRight, .womanInMotorizedWheelchair, + .womanInMotorizedWheelchairFacingRight, .personInManualWheelchair, + .personInManualWheelchairFacingRight, .manInManualWheelchair, + .manInManualWheelchairFacingRight, .womanInManualWheelchair, + .womanInManualWheelchairFacingRight, .runner, .manRunning, .womanRunning, + .personRunningFacingRight, + .womanRunningFacingRight, + .manRunningFacingRight, .dancer, .manDancing, .manInBusinessSuitLevitating, @@ -540,7 +568,6 @@ extension Emoji { .womanHeartMan, .manHeartMan, .womanHeartWoman, - .family, .manWomanBoy, .manWomanGirl, .manWomanGirlBoy, @@ -570,6 +597,11 @@ extension Emoji { .bustInSilhouette, .bustsInSilhouette, .peopleHugging, + .family, + .familyAdultAdultChild, + .familyAdultAdultChildChild, + .familyAdultChild, + .familyAdultChildChild, .footprints, ] case .animals: @@ -661,6 +693,7 @@ extension Emoji { .wing, .blackBird, .goose, + .phoenix, .frog, .crocodile, .turtle, @@ -734,6 +767,7 @@ extension Emoji { .watermelon, .tangerine, .lemon, + .lime, .banana, .pineapple, .mango, @@ -765,6 +799,7 @@ extension Emoji { .chestnut, .gingerRoot, .peaPod, + .brownMushroom, .bread, .croissant, .baguetteBread, @@ -1382,6 +1417,7 @@ extension Emoji { .scales, .probingCane, .link, + .brokenChain, .chains, .hook, .toolbox, @@ -1989,6 +2025,8 @@ extension Emoji { case .faceExhaling: return .smileysAndPeople case .lyingFace: return .smileysAndPeople case .shakingFace: return .smileysAndPeople + case .headShakingHorizontally: return .smileysAndPeople + case .headShakingVertically: return .smileysAndPeople case .relieved: return .smileysAndPeople case .pensive: return .smileysAndPeople case .sleepy: return .smileysAndPeople @@ -2347,24 +2385,42 @@ extension Emoji { case .walking: return .smileysAndPeople case .manWalking: return .smileysAndPeople case .womanWalking: return .smileysAndPeople + case .personWalkingFacingRight: return .smileysAndPeople + case .womanWalkingFacingRight: return .smileysAndPeople + case .manWalkingFacingRight: return .smileysAndPeople case .standingPerson: return .smileysAndPeople case .manStanding: return .smileysAndPeople case .womanStanding: return .smileysAndPeople case .kneelingPerson: return .smileysAndPeople case .manKneeling: return .smileysAndPeople case .womanKneeling: return .smileysAndPeople + case .personKneelingFacingRight: return .smileysAndPeople + case .womanKneelingFacingRight: return .smileysAndPeople + case .manKneelingFacingRight: return .smileysAndPeople case .personWithProbingCane: return .smileysAndPeople + case .personWithWhiteCaneFacingRight: return .smileysAndPeople case .manWithProbingCane: return .smileysAndPeople + case .manWithWhiteCaneFacingRight: return .smileysAndPeople case .womanWithProbingCane: return .smileysAndPeople + case .womanWithWhiteCaneFacingRight: return .smileysAndPeople case .personInMotorizedWheelchair: return .smileysAndPeople + case .personInMotorizedWheelchairFacingRight: return .smileysAndPeople case .manInMotorizedWheelchair: return .smileysAndPeople + case .manInMotorizedWheelchairFacingRight: return .smileysAndPeople case .womanInMotorizedWheelchair: return .smileysAndPeople + case .womanInMotorizedWheelchairFacingRight: return .smileysAndPeople case .personInManualWheelchair: return .smileysAndPeople + case .personInManualWheelchairFacingRight: return .smileysAndPeople case .manInManualWheelchair: return .smileysAndPeople + case .manInManualWheelchairFacingRight: return .smileysAndPeople case .womanInManualWheelchair: return .smileysAndPeople + case .womanInManualWheelchairFacingRight: return .smileysAndPeople case .runner: return .smileysAndPeople case .manRunning: return .smileysAndPeople case .womanRunning: return .smileysAndPeople + case .personRunningFacingRight: return .smileysAndPeople + case .womanRunningFacingRight: return .smileysAndPeople + case .manRunningFacingRight: return .smileysAndPeople case .dancer: return .smileysAndPeople case .manDancing: return .smileysAndPeople case .manInBusinessSuitLevitating: return .smileysAndPeople @@ -2437,7 +2493,6 @@ extension Emoji { case .womanHeartMan: return .smileysAndPeople case .manHeartMan: return .smileysAndPeople case .womanHeartWoman: return .smileysAndPeople - case .family: return .smileysAndPeople case .manWomanBoy: return .smileysAndPeople case .manWomanGirl: return .smileysAndPeople case .manWomanGirlBoy: return .smileysAndPeople @@ -2467,6 +2522,11 @@ extension Emoji { case .bustInSilhouette: return .smileysAndPeople case .bustsInSilhouette: return .smileysAndPeople case .peopleHugging: return .smileysAndPeople + case .family: return .smileysAndPeople + case .familyAdultAdultChild: return .smileysAndPeople + case .familyAdultAdultChildChild: return .smileysAndPeople + case .familyAdultChild: return .smileysAndPeople + case .familyAdultChildChild: return .smileysAndPeople case .footprints: return .smileysAndPeople case .monkeyFace: return .animals case .monkey: return .animals @@ -2555,6 +2615,7 @@ extension Emoji { case .wing: return .animals case .blackBird: return .animals case .goose: return .animals + case .phoenix: return .animals case .frog: return .animals case .crocodile: return .animals case .turtle: return .animals @@ -2625,6 +2686,7 @@ extension Emoji { case .watermelon: return .food case .tangerine: return .food case .lemon: return .food + case .lime: return .food case .banana: return .food case .pineapple: return .food case .mango: return .food @@ -2656,6 +2718,7 @@ extension Emoji { case .chestnut: return .food case .gingerRoot: return .food case .peaPod: return .food + case .brownMushroom: return .food case .bread: return .food case .croissant: return .food case .baguetteBread: return .food @@ -3264,6 +3327,7 @@ extension Emoji { case .scales: return .objects case .probingCane: return .objects case .link: return .objects + case .brokenChain: return .objects case .chains: return .objects case .hook: return .objects case .toolbox: return .objects @@ -3821,4 +3885,3 @@ extension Emoji { } } } -// swiftlint:disable all diff --git a/Session/Emoji/Emoji+Name.swift b/Session/Emoji/Emoji+Name.swift index 2ddb050adb..2b6abe71f9 100644 --- a/Session/Emoji/Emoji+Name.swift +++ b/Session/Emoji/Emoji+Name.swift @@ -1,9 +1,11 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. - +// // swiftlint:disable all // stringlint:disable +import Foundation + extension Emoji { var name: String { switch self { @@ -57,6 +59,8 @@ extension Emoji { case .faceExhaling: return "face exhaling, face_exhaling, faceexhaling" case .lyingFace: return "lying face, lying_face, lyingface" case .shakingFace: return "shaking face, shaking_face, shakingface" + case .headShakingHorizontally: return "head shaking horizontally, head_shaking_horizontally, headshakinghorizontally" + case .headShakingVertically: return "head shaking vertically, head_shaking_vertically, headshakingvertically" case .relieved: return "relieved, relieved face" case .pensive: return "pensive, pensive face" case .sleepy: return "sleepy, sleepy face" @@ -415,24 +419,42 @@ extension Emoji { case .walking: return "pedestrian, walking" case .manWalking: return "man walking, man-walking, manwalking" case .womanWalking: return "woman walking, woman-walking, womanwalking" + case .personWalkingFacingRight: return "person walking facing right, person_walking_facing_right, personwalkingfacingright" + case .womanWalkingFacingRight: return "woman walking facing right, woman_walking_facing_right, womanwalkingfacingright" + case .manWalkingFacingRight: return "man walking facing right, man_walking_facing_right, manwalkingfacingright" case .standingPerson: return "standing person, standing_person, standingperson" case .manStanding: return "man standing, man_standing, manstanding" case .womanStanding: return "woman standing, woman_standing, womanstanding" case .kneelingPerson: return "kneeling person, kneeling_person, kneelingperson" case .manKneeling: return "man kneeling, man_kneeling, mankneeling" case .womanKneeling: return "woman kneeling, woman_kneeling, womankneeling" + case .personKneelingFacingRight: return "person kneeling facing right, person_kneeling_facing_right, personkneelingfacingright" + case .womanKneelingFacingRight: return "woman kneeling facing right, woman_kneeling_facing_right, womankneelingfacingright" + case .manKneelingFacingRight: return "man kneeling facing right, man_kneeling_facing_right, mankneelingfacingright" case .personWithProbingCane: return "person with white cane, person_with_probing_cane, personwithprobingcane" + case .personWithWhiteCaneFacingRight: return "person with white cane facing right, person_with_white_cane_facing_right, personwithwhitecanefacingright" case .manWithProbingCane: return "man with white cane, man_with_probing_cane, manwithprobingcane" + case .manWithWhiteCaneFacingRight: return "man with white cane facing right, man_with_white_cane_facing_right, manwithwhitecanefacingright" case .womanWithProbingCane: return "woman with white cane, woman_with_probing_cane, womanwithprobingcane" + case .womanWithWhiteCaneFacingRight: return "woman with white cane facing right, woman_with_white_cane_facing_right, womanwithwhitecanefacingright" case .personInMotorizedWheelchair: return "person in motorized wheelchair, person_in_motorized_wheelchair, personinmotorizedwheelchair" + case .personInMotorizedWheelchairFacingRight: return "person in motorized wheelchair facing right, person_in_motorized_wheelchair_facing_right, personinmotorizedwheelchairfacingright" case .manInMotorizedWheelchair: return "man in motorized wheelchair, man_in_motorized_wheelchair, maninmotorizedwheelchair" + case .manInMotorizedWheelchairFacingRight: return "man in motorized wheelchair facing right, man_in_motorized_wheelchair_facing_right, maninmotorizedwheelchairfacingright" case .womanInMotorizedWheelchair: return "woman in motorized wheelchair, woman_in_motorized_wheelchair, womaninmotorizedwheelchair" + case .womanInMotorizedWheelchairFacingRight: return "woman in motorized wheelchair facing right, woman_in_motorized_wheelchair_facing_right, womaninmotorizedwheelchairfacingright" case .personInManualWheelchair: return "person in manual wheelchair, person_in_manual_wheelchair, personinmanualwheelchair" + case .personInManualWheelchairFacingRight: return "person in manual wheelchair facing right, person_in_manual_wheelchair_facing_right, personinmanualwheelchairfacingright" case .manInManualWheelchair: return "man in manual wheelchair, man_in_manual_wheelchair, maninmanualwheelchair" + case .manInManualWheelchairFacingRight: return "man in manual wheelchair facing right, man_in_manual_wheelchair_facing_right, maninmanualwheelchairfacingright" case .womanInManualWheelchair: return "woman in manual wheelchair, woman_in_manual_wheelchair, womaninmanualwheelchair" + case .womanInManualWheelchairFacingRight: return "woman in manual wheelchair facing right, woman_in_manual_wheelchair_facing_right, womaninmanualwheelchairfacingright" case .runner: return "runner, running" case .manRunning: return "man running, man-running, manrunning" case .womanRunning: return "woman running, woman-running, womanrunning" + case .personRunningFacingRight: return "person running facing right, person_running_facing_right, personrunningfacingright" + case .womanRunningFacingRight: return "woman running facing right, woman_running_facing_right, womanrunningfacingright" + case .manRunningFacingRight: return "man running facing right, man_running_facing_right, manrunningfacingright" case .dancer: return "dancer" case .manDancing: return "man dancing, man_dancing, mandancing" case .manInBusinessSuitLevitating: return "man_in_business_suit_levitating, maninbusinesssuitlevitating, person in suit levitating" @@ -505,7 +527,6 @@ extension Emoji { case .womanHeartMan: return "couple with heart: woman, man, woman-heart-man, womanheartman" case .manHeartMan: return "couple with heart: man, man, man-heart-man, manheartman" case .womanHeartWoman: return "couple with heart: woman, woman, woman-heart-woman, womanheartwoman" - case .family: return "family" case .manWomanBoy: return "family: man, woman, boy, man-woman-boy, manwomanboy" case .manWomanGirl: return "family: man, woman, girl, man-woman-girl, manwomangirl" case .manWomanGirlBoy: return "family: man, woman, girl, boy, man-woman-girl-boy, manwomangirlboy" @@ -535,6 +556,11 @@ extension Emoji { case .bustInSilhouette: return "bust in silhouette, bust_in_silhouette, bustinsilhouette" case .bustsInSilhouette: return "busts in silhouette, busts_in_silhouette, bustsinsilhouette" case .peopleHugging: return "people hugging, people_hugging, peoplehugging" + case .family: return "family" + case .familyAdultAdultChild: return "family: adult, adult, child, family_adult_adult_child, familyadultadultchild" + case .familyAdultAdultChildChild: return "family: adult, adult, child, child, family_adult_adult_child_child, familyadultadultchildchild" + case .familyAdultChild: return "family: adult, child, family_adult_child, familyadultchild" + case .familyAdultChildChild: return "family: adult, child, child, family_adult_child_child, familyadultchildchild" case .footprints: return "footprints" case .skinTone2: return "emoji modifier fitzpatrick type-1-2, skin-tone-2, skintone2" case .skinTone3: return "emoji modifier fitzpatrick type-3, skin-tone-3, skintone3" @@ -628,6 +654,7 @@ extension Emoji { case .wing: return "wing" case .blackBird: return "black bird, black_bird, blackbird" case .goose: return "goose" + case .phoenix: return "phoenix" case .frog: return "frog, frog face" case .crocodile: return "crocodile" case .turtle: return "turtle" @@ -698,6 +725,7 @@ extension Emoji { case .watermelon: return "watermelon" case .tangerine: return "tangerine" case .lemon: return "lemon" + case .lime: return "lime" case .banana: return "banana" case .pineapple: return "pineapple" case .mango: return "mango" @@ -729,6 +757,7 @@ extension Emoji { case .chestnut: return "chestnut" case .gingerRoot: return "ginger root, ginger_root, gingerroot" case .peaPod: return "pea pod, pea_pod, peapod" + case .brownMushroom: return "brown mushroom, brown_mushroom, brownmushroom" case .bread: return "bread" case .croissant: return "croissant" case .baguetteBread: return "baguette bread, baguette_bread, baguettebread" @@ -1337,6 +1366,7 @@ extension Emoji { case .scales: return "balance scale, scales" case .probingCane: return "probing cane, probing_cane, probingcane" case .link: return "link, link symbol" + case .brokenChain: return "broken chain, broken_chain, brokenchain" case .chains: return "chains" case .hook: return "hook" case .toolbox: return "toolbox" @@ -1852,7 +1882,7 @@ extension Emoji { case .flagTm: return "flag-tm, flagtm, turkmenistan flag" case .flagTn: return "flag-tn, flagtn, tunisia flag" case .flagTo: return "flag-to, flagto, tonga flag" - case .flagTr: return "flag-tr, flagtr, turkey flag" + case .flagTr: return "flag-tr, flagtr, türkiye flag" case .flagTt: return "flag-tt, flagtt, trinidad & tobago flag" case .flagTv: return "flag-tv, flagtv, tuvalu flag" case .flagTw: return "flag-tw, flagtw, taiwan flag" @@ -1885,4 +1915,3 @@ extension Emoji { } } } -// swiftlint:disable all diff --git a/Session/Emoji/Emoji+SkinTones.swift b/Session/Emoji/Emoji+SkinTones.swift index f1fb18434f..9f34de6151 100644 --- a/Session/Emoji/Emoji+SkinTones.swift +++ b/Session/Emoji/Emoji+SkinTones.swift @@ -1,9 +1,11 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. - +// // swiftlint:disable all // stringlint:disable +import Foundation + extension Emoji { enum SkinTone: String, CaseIterable, Equatable { case light = "🏻" @@ -1841,6 +1843,30 @@ extension Emoji { [.mediumDark]: "🚶🏾‍♀️", [.dark]: "🚶🏿‍♀️", ] + case .personWalkingFacingRight: + return [ + [.light]: "🚶🏻‍➡️", + [.mediumLight]: "🚶🏼‍➡️", + [.medium]: "🚶🏽‍➡️", + [.mediumDark]: "🚶🏾‍➡️", + [.dark]: "🚶🏿‍➡️", + ] + case .womanWalkingFacingRight: + return [ + [.light]: "🚶🏻‍♀️‍➡️", + [.mediumLight]: "🚶🏼‍♀️‍➡️", + [.medium]: "🚶🏽‍♀️‍➡️", + [.mediumDark]: "🚶🏾‍♀️‍➡️", + [.dark]: "🚶🏿‍♀️‍➡️", + ] + case .manWalkingFacingRight: + return [ + [.light]: "🚶🏻‍♂️‍➡️", + [.mediumLight]: "🚶🏼‍♂️‍➡️", + [.medium]: "🚶🏽‍♂️‍➡️", + [.mediumDark]: "🚶🏾‍♂️‍➡️", + [.dark]: "🚶🏿‍♂️‍➡️", + ] case .standingPerson: return [ [.light]: "🧍🏻", @@ -1889,6 +1915,30 @@ extension Emoji { [.mediumDark]: "🧎🏾‍♀️", [.dark]: "🧎🏿‍♀️", ] + case .personKneelingFacingRight: + return [ + [.light]: "🧎🏻‍➡️", + [.mediumLight]: "🧎🏼‍➡️", + [.medium]: "🧎🏽‍➡️", + [.mediumDark]: "🧎🏾‍➡️", + [.dark]: "🧎🏿‍➡️", + ] + case .womanKneelingFacingRight: + return [ + [.light]: "🧎🏻‍♀️‍➡️", + [.mediumLight]: "🧎🏼‍♀️‍➡️", + [.medium]: "🧎🏽‍♀️‍➡️", + [.mediumDark]: "🧎🏾‍♀️‍➡️", + [.dark]: "🧎🏿‍♀️‍➡️", + ] + case .manKneelingFacingRight: + return [ + [.light]: "🧎🏻‍♂️‍➡️", + [.mediumLight]: "🧎🏼‍♂️‍➡️", + [.medium]: "🧎🏽‍♂️‍➡️", + [.mediumDark]: "🧎🏾‍♂️‍➡️", + [.dark]: "🧎🏿‍♂️‍➡️", + ] case .personWithProbingCane: return [ [.light]: "🧑🏻‍🦯", @@ -1897,6 +1947,14 @@ extension Emoji { [.mediumDark]: "🧑🏾‍🦯", [.dark]: "🧑🏿‍🦯", ] + case .personWithWhiteCaneFacingRight: + return [ + [.light]: "🧑🏻‍🦯‍➡️", + [.mediumLight]: "🧑🏼‍🦯‍➡️", + [.medium]: "🧑🏽‍🦯‍➡️", + [.mediumDark]: "🧑🏾‍🦯‍➡️", + [.dark]: "🧑🏿‍🦯‍➡️", + ] case .manWithProbingCane: return [ [.light]: "👨🏻‍🦯", @@ -1905,6 +1963,14 @@ extension Emoji { [.mediumDark]: "👨🏾‍🦯", [.dark]: "👨🏿‍🦯", ] + case .manWithWhiteCaneFacingRight: + return [ + [.light]: "👨🏻‍🦯‍➡️", + [.mediumLight]: "👨🏼‍🦯‍➡️", + [.medium]: "👨🏽‍🦯‍➡️", + [.mediumDark]: "👨🏾‍🦯‍➡️", + [.dark]: "👨🏿‍🦯‍➡️", + ] case .womanWithProbingCane: return [ [.light]: "👩🏻‍🦯", @@ -1913,6 +1979,14 @@ extension Emoji { [.mediumDark]: "👩🏾‍🦯", [.dark]: "👩🏿‍🦯", ] + case .womanWithWhiteCaneFacingRight: + return [ + [.light]: "👩🏻‍🦯‍➡️", + [.mediumLight]: "👩🏼‍🦯‍➡️", + [.medium]: "👩🏽‍🦯‍➡️", + [.mediumDark]: "👩🏾‍🦯‍➡️", + [.dark]: "👩🏿‍🦯‍➡️", + ] case .personInMotorizedWheelchair: return [ [.light]: "🧑🏻‍🦼", @@ -1921,6 +1995,14 @@ extension Emoji { [.mediumDark]: "🧑🏾‍🦼", [.dark]: "🧑🏿‍🦼", ] + case .personInMotorizedWheelchairFacingRight: + return [ + [.light]: "🧑🏻‍🦼‍➡️", + [.mediumLight]: "🧑🏼‍🦼‍➡️", + [.medium]: "🧑🏽‍🦼‍➡️", + [.mediumDark]: "🧑🏾‍🦼‍➡️", + [.dark]: "🧑🏿‍🦼‍➡️", + ] case .manInMotorizedWheelchair: return [ [.light]: "👨🏻‍🦼", @@ -1929,6 +2011,14 @@ extension Emoji { [.mediumDark]: "👨🏾‍🦼", [.dark]: "👨🏿‍🦼", ] + case .manInMotorizedWheelchairFacingRight: + return [ + [.light]: "👨🏻‍🦼‍➡️", + [.mediumLight]: "👨🏼‍🦼‍➡️", + [.medium]: "👨🏽‍🦼‍➡️", + [.mediumDark]: "👨🏾‍🦼‍➡️", + [.dark]: "👨🏿‍🦼‍➡️", + ] case .womanInMotorizedWheelchair: return [ [.light]: "👩🏻‍🦼", @@ -1937,6 +2027,14 @@ extension Emoji { [.mediumDark]: "👩🏾‍🦼", [.dark]: "👩🏿‍🦼", ] + case .womanInMotorizedWheelchairFacingRight: + return [ + [.light]: "👩🏻‍🦼‍➡️", + [.mediumLight]: "👩🏼‍🦼‍➡️", + [.medium]: "👩🏽‍🦼‍➡️", + [.mediumDark]: "👩🏾‍🦼‍➡️", + [.dark]: "👩🏿‍🦼‍➡️", + ] case .personInManualWheelchair: return [ [.light]: "🧑🏻‍🦽", @@ -1945,6 +2043,14 @@ extension Emoji { [.mediumDark]: "🧑🏾‍🦽", [.dark]: "🧑🏿‍🦽", ] + case .personInManualWheelchairFacingRight: + return [ + [.light]: "🧑🏻‍🦽‍➡️", + [.mediumLight]: "🧑🏼‍🦽‍➡️", + [.medium]: "🧑🏽‍🦽‍➡️", + [.mediumDark]: "🧑🏾‍🦽‍➡️", + [.dark]: "🧑🏿‍🦽‍➡️", + ] case .manInManualWheelchair: return [ [.light]: "👨🏻‍🦽", @@ -1953,6 +2059,14 @@ extension Emoji { [.mediumDark]: "👨🏾‍🦽", [.dark]: "👨🏿‍🦽", ] + case .manInManualWheelchairFacingRight: + return [ + [.light]: "👨🏻‍🦽‍➡️", + [.mediumLight]: "👨🏼‍🦽‍➡️", + [.medium]: "👨🏽‍🦽‍➡️", + [.mediumDark]: "👨🏾‍🦽‍➡️", + [.dark]: "👨🏿‍🦽‍➡️", + ] case .womanInManualWheelchair: return [ [.light]: "👩🏻‍🦽", @@ -1961,6 +2075,14 @@ extension Emoji { [.mediumDark]: "👩🏾‍🦽", [.dark]: "👩🏿‍🦽", ] + case .womanInManualWheelchairFacingRight: + return [ + [.light]: "👩🏻‍🦽‍➡️", + [.mediumLight]: "👩🏼‍🦽‍➡️", + [.medium]: "👩🏽‍🦽‍➡️", + [.mediumDark]: "👩🏾‍🦽‍➡️", + [.dark]: "👩🏿‍🦽‍➡️", + ] case .runner: return [ [.light]: "🏃🏻", @@ -1985,6 +2107,30 @@ extension Emoji { [.mediumDark]: "🏃🏾‍♀️", [.dark]: "🏃🏿‍♀️", ] + case .personRunningFacingRight: + return [ + [.light]: "🏃🏻‍➡️", + [.mediumLight]: "🏃🏼‍➡️", + [.medium]: "🏃🏽‍➡️", + [.mediumDark]: "🏃🏾‍➡️", + [.dark]: "🏃🏿‍➡️", + ] + case .womanRunningFacingRight: + return [ + [.light]: "🏃🏻‍♀️‍➡️", + [.mediumLight]: "🏃🏼‍♀️‍➡️", + [.medium]: "🏃🏽‍♀️‍➡️", + [.mediumDark]: "🏃🏾‍♀️‍➡️", + [.dark]: "🏃🏿‍♀️‍➡️", + ] + case .manRunningFacingRight: + return [ + [.light]: "🏃🏻‍♂️‍➡️", + [.mediumLight]: "🏃🏼‍♂️‍➡️", + [.medium]: "🏃🏽‍♂️‍➡️", + [.mediumDark]: "🏃🏾‍♂️‍➡️", + [.dark]: "🏃🏿‍♂️‍➡️", + ] case .dancer: return [ [.light]: "💃🏻", @@ -2741,4 +2887,3 @@ extension Emoji { } } } -// swiftlint:disable all diff --git a/Session/Emoji/Emoji.swift b/Session/Emoji/Emoji.swift index aa8352ca24..a315b53273 100644 --- a/Session/Emoji/Emoji.swift +++ b/Session/Emoji/Emoji.swift @@ -1,9 +1,11 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. - +// // swiftlint:disable all // stringlint:disable +import Foundation + /// A sorted representation of all available emoji enum Emoji: String, CaseIterable, Equatable { case grinning = "😀" @@ -56,6 +58,8 @@ enum Emoji: String, CaseIterable, Equatable { case faceExhaling = "😮‍💨" case lyingFace = "🤥" case shakingFace = "🫨" + case headShakingHorizontally = "🙂‍↔️" + case headShakingVertically = "🙂‍↕️" case relieved = "😌" case pensive = "😔" case sleepy = "😪" @@ -414,24 +418,42 @@ enum Emoji: String, CaseIterable, Equatable { case walking = "🚶" case manWalking = "🚶‍♂️" case womanWalking = "🚶‍♀️" + case personWalkingFacingRight = "🚶‍➡️" + case womanWalkingFacingRight = "🚶‍♀️‍➡️" + case manWalkingFacingRight = "🚶‍♂️‍➡️" case standingPerson = "🧍" case manStanding = "🧍‍♂️" case womanStanding = "🧍‍♀️" case kneelingPerson = "🧎" case manKneeling = "🧎‍♂️" case womanKneeling = "🧎‍♀️" + case personKneelingFacingRight = "🧎‍➡️" + case womanKneelingFacingRight = "🧎‍♀️‍➡️" + case manKneelingFacingRight = "🧎‍♂️‍➡️" case personWithProbingCane = "🧑‍🦯" + case personWithWhiteCaneFacingRight = "🧑‍🦯‍➡️" case manWithProbingCane = "👨‍🦯" + case manWithWhiteCaneFacingRight = "👨‍🦯‍➡️" case womanWithProbingCane = "👩‍🦯" + case womanWithWhiteCaneFacingRight = "👩‍🦯‍➡️" case personInMotorizedWheelchair = "🧑‍🦼" + case personInMotorizedWheelchairFacingRight = "🧑‍🦼‍➡️" case manInMotorizedWheelchair = "👨‍🦼" + case manInMotorizedWheelchairFacingRight = "👨‍🦼‍➡️" case womanInMotorizedWheelchair = "👩‍🦼" + case womanInMotorizedWheelchairFacingRight = "👩‍🦼‍➡️" case personInManualWheelchair = "🧑‍🦽" + case personInManualWheelchairFacingRight = "🧑‍🦽‍➡️" case manInManualWheelchair = "👨‍🦽" + case manInManualWheelchairFacingRight = "👨‍🦽‍➡️" case womanInManualWheelchair = "👩‍🦽" + case womanInManualWheelchairFacingRight = "👩‍🦽‍➡️" case runner = "🏃" case manRunning = "🏃‍♂️" case womanRunning = "🏃‍♀️" + case personRunningFacingRight = "🏃‍➡️" + case womanRunningFacingRight = "🏃‍♀️‍➡️" + case manRunningFacingRight = "🏃‍♂️‍➡️" case dancer = "💃" case manDancing = "🕺" case manInBusinessSuitLevitating = "🕴️" @@ -504,7 +526,6 @@ enum Emoji: String, CaseIterable, Equatable { case womanHeartMan = "👩‍❤️‍👨" case manHeartMan = "👨‍❤️‍👨" case womanHeartWoman = "👩‍❤️‍👩" - case family = "👪" case manWomanBoy = "👨‍👩‍👦" case manWomanGirl = "👨‍👩‍👧" case manWomanGirlBoy = "👨‍👩‍👧‍👦" @@ -534,6 +555,11 @@ enum Emoji: String, CaseIterable, Equatable { case bustInSilhouette = "👤" case bustsInSilhouette = "👥" case peopleHugging = "🫂" + case family = "👪" + case familyAdultAdultChild = "🧑‍🧑‍🧒" + case familyAdultAdultChildChild = "🧑‍🧑‍🧒‍🧒" + case familyAdultChild = "🧑‍🧒" + case familyAdultChildChild = "🧑‍🧒‍🧒" case footprints = "👣" case skinTone2 = "🏻" case skinTone3 = "🏼" @@ -627,6 +653,7 @@ enum Emoji: String, CaseIterable, Equatable { case wing = "🪽" case blackBird = "🐦‍⬛" case goose = "🪿" + case phoenix = "🐦‍🔥" case frog = "🐸" case crocodile = "🐊" case turtle = "🐢" @@ -697,6 +724,7 @@ enum Emoji: String, CaseIterable, Equatable { case watermelon = "🍉" case tangerine = "🍊" case lemon = "🍋" + case lime = "🍋‍🟩" case banana = "🍌" case pineapple = "🍍" case mango = "🥭" @@ -728,6 +756,7 @@ enum Emoji: String, CaseIterable, Equatable { case chestnut = "🌰" case gingerRoot = "🫚" case peaPod = "🫛" + case brownMushroom = "🍄‍🟫" case bread = "🍞" case croissant = "🥐" case baguetteBread = "🥖" @@ -1336,6 +1365,7 @@ enum Emoji: String, CaseIterable, Equatable { case scales = "⚖️" case probingCane = "🦯" case link = "🔗" + case brokenChain = "⛓️‍💥" case chains = "⛓️" case hook = "🪝" case toolbox = "🧰" @@ -1882,4 +1912,3 @@ enum Emoji: String, CaseIterable, Equatable { case flagScotland = "🏴󠁧󠁢󠁳󠁣󠁴󠁿" case flagWales = "🏴󠁧󠁢󠁷󠁬󠁳󠁿" } -// swiftlint:disable all diff --git a/Session/Emoji/EmojiWithSkinTones+String.swift b/Session/Emoji/EmojiWithSkinTones+String.swift index e1b1c84daa..3572ff1249 100644 --- a/Session/Emoji/EmojiWithSkinTones+String.swift +++ b/Session/Emoji/EmojiWithSkinTones+String.swift @@ -1,9 +1,11 @@ // This file is generated by EmojiGenerator.swift, do not manually edit it. - +// // swiftlint:disable all // stringlint:disable +import Foundation + extension EmojiWithSkinTones { init?(rawValue: String) { guard rawValue.isSingleEmoji else { return nil } @@ -75,16 +77,19 @@ extension EmojiWithSkinTones { case 1934: self = EmojiWithSkinTones.emojiFrom1934(rawValue) case 1935: self = EmojiWithSkinTones.emojiFrom1935(rawValue) case 1937: self = EmojiWithSkinTones.emojiFrom1937(rawValue) + case 2104: self = EmojiWithSkinTones.emojiFrom2104(rawValue) case 2109: self = EmojiWithSkinTones.emojiFrom2109(rawValue) case 2111: self = EmojiWithSkinTones.emojiFrom2111(rawValue) case 2112: self = EmojiWithSkinTones.emojiFrom2112(rawValue) case 2113: self = EmojiWithSkinTones.emojiFrom2113(rawValue) case 2116: self = EmojiWithSkinTones.emojiFrom2116(rawValue) case 2117: self = EmojiWithSkinTones.emojiFrom2117(rawValue) + case 2120: self = EmojiWithSkinTones.emojiFrom2120(rawValue) case 2123: self = EmojiWithSkinTones.emojiFrom2123(rawValue) case 2125: self = EmojiWithSkinTones.emojiFrom2125(rawValue) case 2126: self = EmojiWithSkinTones.emojiFrom2126(rawValue) case 2127: self = EmojiWithSkinTones.emojiFrom2127(rawValue) + case 2128: self = EmojiWithSkinTones.emojiFrom2128(rawValue) case 2129: self = EmojiWithSkinTones.emojiFrom2129(rawValue) case 2210: self = EmojiWithSkinTones.emojiFrom2210(rawValue) case 2549: self = EmojiWithSkinTones.emojiFrom2549(rawValue) @@ -105,8 +110,10 @@ extension EmojiWithSkinTones { case 2641: self = EmojiWithSkinTones.emojiFrom2641(rawValue) case 2642: self = EmojiWithSkinTones.emojiFrom2642(rawValue) case 2644: self = EmojiWithSkinTones.emojiFrom2644(rawValue) + case 2645: self = EmojiWithSkinTones.emojiFrom2645(rawValue) case 2646: self = EmojiWithSkinTones.emojiFrom2646(rawValue) case 2649: self = EmojiWithSkinTones.emojiFrom2649(rawValue) + case 2650: self = EmojiWithSkinTones.emojiFrom2650(rawValue) case 2655: self = EmojiWithSkinTones.emojiFrom2655(rawValue) case 2656: self = EmojiWithSkinTones.emojiFrom2656(rawValue) case 2657: self = EmojiWithSkinTones.emojiFrom2657(rawValue) @@ -117,6 +124,9 @@ extension EmojiWithSkinTones { case 2760: self = EmojiWithSkinTones.emojiFrom2760(rawValue) case 2761: self = EmojiWithSkinTones.emojiFrom2761(rawValue) case 2764: self = EmojiWithSkinTones.emojiFrom2764(rawValue) + case 2943: self = EmojiWithSkinTones.emojiFrom2943(rawValue) + case 2951: self = EmojiWithSkinTones.emojiFrom2951(rawValue) + case 2959: self = EmojiWithSkinTones.emojiFrom2959(rawValue) case 3289: self = EmojiWithSkinTones.emojiFrom3289(rawValue) case 3295: self = EmojiWithSkinTones.emojiFrom3295(rawValue) case 3389: self = EmojiWithSkinTones.emojiFrom3389(rawValue) @@ -126,12 +136,16 @@ extension EmojiWithSkinTones { case 3394: self = EmojiWithSkinTones.emojiFrom3394(rawValue) case 3396: self = EmojiWithSkinTones.emojiFrom3396(rawValue) case 3397: self = EmojiWithSkinTones.emojiFrom3397(rawValue) + case 3400: self = EmojiWithSkinTones.emojiFrom3400(rawValue) case 3403: self = EmojiWithSkinTones.emojiFrom3403(rawValue) case 3404: self = EmojiWithSkinTones.emojiFrom3404(rawValue) case 3405: self = EmojiWithSkinTones.emojiFrom3405(rawValue) case 3406: self = EmojiWithSkinTones.emojiFrom3406(rawValue) case 3407: self = EmojiWithSkinTones.emojiFrom3407(rawValue) + case 3408: self = EmojiWithSkinTones.emojiFrom3408(rawValue) case 3477: self = EmojiWithSkinTones.emojiFrom3477(rawValue) + case 3491: self = EmojiWithSkinTones.emojiFrom3491(rawValue) + case 3505: self = EmojiWithSkinTones.emojiFrom3505(rawValue) case 3921: self = EmojiWithSkinTones.emojiFrom3921(rawValue) case 3922: self = EmojiWithSkinTones.emojiFrom3922(rawValue) case 3924: self = EmojiWithSkinTones.emojiFrom3924(rawValue) @@ -149,9 +163,16 @@ extension EmojiWithSkinTones { case 3951: self = EmojiWithSkinTones.emojiFrom3951(rawValue) case 4007: self = EmojiWithSkinTones.emojiFrom4007(rawValue) case 4046: self = EmojiWithSkinTones.emojiFrom4046(rawValue) + case 4048: self = EmojiWithSkinTones.emojiFrom4048(rawValue) + case 4223: self = EmojiWithSkinTones.emojiFrom4223(rawValue) + case 4231: self = EmojiWithSkinTones.emojiFrom4231(rawValue) + case 4239: self = EmojiWithSkinTones.emojiFrom4239(rawValue) + case 4771: self = EmojiWithSkinTones.emojiFrom4771(rawValue) + case 4785: self = EmojiWithSkinTones.emojiFrom4785(rawValue) case 4840: self = EmojiWithSkinTones.emojiFrom4840(rawValue) case 5237: self = EmojiWithSkinTones.emojiFrom5237(rawValue) case 5370: self = EmojiWithSkinTones.emojiFrom5370(rawValue) + case 5425: self = EmojiWithSkinTones.emojiFrom5425(rawValue) case 6037: self = EmojiWithSkinTones.emojiFrom6037(rawValue) case 6065: self = EmojiWithSkinTones.emojiFrom6065(rawValue) case 6579: self = EmojiWithSkinTones.emojiFrom6579(rawValue) @@ -961,9 +982,9 @@ extension EmojiWithSkinTones { "👬": EmojiWithSkinTones(baseEmoji: .twoMenHoldingHands, skinTones: nil), "💏": EmojiWithSkinTones(baseEmoji: .personKissPerson, skinTones: nil), "💑": EmojiWithSkinTones(baseEmoji: .personHeartPerson, skinTones: nil), - "👪": EmojiWithSkinTones(baseEmoji: .family, skinTones: nil), "👤": EmojiWithSkinTones(baseEmoji: .bustInSilhouette, skinTones: nil), "👥": EmojiWithSkinTones(baseEmoji: .bustsInSilhouette, skinTones: nil), + "👪": EmojiWithSkinTones(baseEmoji: .family, skinTones: nil), "💐": EmojiWithSkinTones(baseEmoji: .bouquet, skinTones: nil), "💮": EmojiWithSkinTones(baseEmoji: .whiteFlower, skinTones: nil), "💒": EmojiWithSkinTones(baseEmoji: .wedding, skinTones: nil), @@ -1993,6 +2014,14 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2104(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🙂‍↔️": EmojiWithSkinTones(baseEmoji: .headShakingHorizontally, skinTones: nil), + "🙂‍↕️": EmojiWithSkinTones(baseEmoji: .headShakingVertically, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom2109(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "🏃‍♂️": EmojiWithSkinTones(baseEmoji: .manRunning, skinTones: nil), @@ -2046,7 +2075,9 @@ extension EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👨‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: nil), "👩‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: nil), - "🐻‍❄️": EmojiWithSkinTones(baseEmoji: .polarBear, skinTones: nil) + "🏃‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: nil), + "🐻‍❄️": EmojiWithSkinTones(baseEmoji: .polarBear, skinTones: nil), + "⛓️‍💥": EmojiWithSkinTones(baseEmoji: .brokenChain, skinTones: nil) ] return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } @@ -2084,6 +2115,13 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2120(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom2123(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "🤦‍♂️": EmojiWithSkinTones(baseEmoji: .manFacepalming, skinTones: nil), @@ -2159,6 +2197,13 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2128(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧎‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom2129(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "❤️‍🩹": EmojiWithSkinTones(baseEmoji: .mendingHeart, skinTones: nil) @@ -3197,6 +3242,13 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2645(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🐦‍🔥": EmojiWithSkinTones(baseEmoji: .phoenix, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom2646(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👨‍🔧": EmojiWithSkinTones(baseEmoji: .maleMechanic, skinTones: nil), @@ -3219,6 +3271,14 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2650(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🍋‍🟩": EmojiWithSkinTones(baseEmoji: .lime, skinTones: nil), + "🍄‍🟫": EmojiWithSkinTones(baseEmoji: .brownMushroom, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom2655(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "🧑‍🎓": EmojiWithSkinTones(baseEmoji: .student, skinTones: nil), @@ -3293,7 +3353,8 @@ extension EmojiWithSkinTones { "🧑‍🦲": EmojiWithSkinTones(baseEmoji: .baldPerson, skinTones: nil), "🧑‍🦯": EmojiWithSkinTones(baseEmoji: .personWithProbingCane, skinTones: nil), "🧑‍🦼": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchair, skinTones: nil), - "🧑‍🦽": EmojiWithSkinTones(baseEmoji: .personInManualWheelchair, skinTones: nil) + "🧑‍🦽": EmojiWithSkinTones(baseEmoji: .personInManualWheelchair, skinTones: nil), + "🧑‍🧒": EmojiWithSkinTones(baseEmoji: .familyAdultChild, skinTones: nil) ] return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } @@ -3323,6 +3384,30 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom2943(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏃‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: nil), + "🏃‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2951(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: nil), + "🚶‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom2959(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧎‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: nil), + "🧎‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom3289(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "🏳️‍🌈": EmojiWithSkinTones(baseEmoji: .rainbowFlag, skinTones: nil) @@ -3519,14 +3604,19 @@ extension EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👨🏻‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.light]), "👩🏻‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.light]), + "🏃🏻‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: [.light]), "👨🏼‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.mediumLight]), "👩🏼‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.mediumLight]), + "🏃🏼‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: [.mediumLight]), "👨🏽‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.medium]), "👩🏽‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.medium]), + "🏃🏽‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: [.medium]), "👨🏾‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.mediumDark]), "👩🏾‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.mediumDark]), + "🏃🏾‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: [.mediumDark]), "👨🏿‍✈️": EmojiWithSkinTones(baseEmoji: .malePilot, skinTones: [.dark]), - "👩🏿‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.dark]) + "👩🏿‍✈️": EmojiWithSkinTones(baseEmoji: .femalePilot, skinTones: [.dark]), + "🏃🏿‍➡️": EmojiWithSkinTones(baseEmoji: .personRunningFacingRight, skinTones: [.dark]) ] return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } @@ -3659,6 +3749,17 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom3400(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶🏻‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: [.light]), + "🚶🏼‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: [.mediumLight]), + "🚶🏽‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: [.medium]), + "🚶🏾‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: [.mediumDark]), + "🚶🏿‍➡️": EmojiWithSkinTones(baseEmoji: .personWalkingFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom3403(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "🤦🏻‍♂️": EmojiWithSkinTones(baseEmoji: .manFacepalming, skinTones: [.light]), @@ -3914,6 +4015,17 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom3408(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧎🏻‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: [.light]), + "🧎🏼‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: [.mediumLight]), + "🧎🏽‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: [.medium]), + "🧎🏾‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: [.mediumDark]), + "🧎🏿‍➡️": EmojiWithSkinTones(baseEmoji: .personKneelingFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom3477(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👩‍❤️‍👨": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: nil), @@ -3923,6 +4035,27 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom3491(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: nil), + "👩‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: nil), + "👨‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: nil), + "👩‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: nil), + "👨‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: nil), + "👩‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom3505(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: nil), + "🧑‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: nil), + "🧑‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom3921(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👨🏻‍🎓": EmojiWithSkinTones(baseEmoji: .maleStudent, skinTones: [.light]), @@ -4359,6 +4492,119 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom4048(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🧑‍🧒": EmojiWithSkinTones(baseEmoji: .familyAdultAdultChild, skinTones: nil), + "🧑‍🧒‍🧒": EmojiWithSkinTones(baseEmoji: .familyAdultChildChild, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4223(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🏃🏻‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: [.light]), + "🏃🏻‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: [.light]), + "🏃🏼‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: [.mediumLight]), + "🏃🏼‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: [.mediumLight]), + "🏃🏽‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: [.medium]), + "🏃🏽‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: [.medium]), + "🏃🏾‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: [.mediumDark]), + "🏃🏾‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: [.mediumDark]), + "🏃🏿‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanRunningFacingRight, skinTones: [.dark]), + "🏃🏿‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manRunningFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4231(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🚶🏻‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: [.light]), + "🚶🏻‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: [.light]), + "🚶🏼‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: [.mediumLight]), + "🚶🏼‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: [.mediumLight]), + "🚶🏽‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: [.medium]), + "🚶🏽‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: [.medium]), + "🚶🏾‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: [.mediumDark]), + "🚶🏾‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: [.mediumDark]), + "🚶🏿‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanWalkingFacingRight, skinTones: [.dark]), + "🚶🏿‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manWalkingFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4239(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧎🏻‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: [.light]), + "🧎🏻‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: [.light]), + "🧎🏼‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: [.mediumLight]), + "🧎🏼‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: [.mediumLight]), + "🧎🏽‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: [.medium]), + "🧎🏽‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: [.medium]), + "🧎🏾‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: [.mediumDark]), + "🧎🏾‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: [.mediumDark]), + "🧎🏿‍♀️‍➡️": EmojiWithSkinTones(baseEmoji: .womanKneelingFacingRight, skinTones: [.dark]), + "🧎🏿‍♂️‍➡️": EmojiWithSkinTones(baseEmoji: .manKneelingFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4771(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "👨🏻‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: [.light]), + "👩🏻‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: [.light]), + "👨🏻‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: [.light]), + "👩🏻‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: [.light]), + "👨🏻‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: [.light]), + "👩🏻‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: [.light]), + "👨🏼‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: [.mediumLight]), + "👩🏼‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: [.mediumLight]), + "👨🏼‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: [.mediumLight]), + "👩🏼‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: [.mediumLight]), + "👨🏼‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: [.mediumLight]), + "👩🏼‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: [.mediumLight]), + "👨🏽‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: [.medium]), + "👩🏽‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: [.medium]), + "👨🏽‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: [.medium]), + "👩🏽‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: [.medium]), + "👨🏽‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: [.medium]), + "👩🏽‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: [.medium]), + "👨🏾‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: [.mediumDark]), + "👩🏾‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: [.mediumDark]), + "👨🏾‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: [.mediumDark]), + "👩🏾‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: [.mediumDark]), + "👨🏾‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: [.mediumDark]), + "👩🏾‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: [.mediumDark]), + "👨🏿‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .manWithWhiteCaneFacingRight, skinTones: [.dark]), + "👩🏿‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .womanWithWhiteCaneFacingRight, skinTones: [.dark]), + "👨🏿‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .manInMotorizedWheelchairFacingRight, skinTones: [.dark]), + "👩🏿‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .womanInMotorizedWheelchairFacingRight, skinTones: [.dark]), + "👨🏿‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .manInManualWheelchairFacingRight, skinTones: [.dark]), + "👩🏿‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .womanInManualWheelchairFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + + private static func emojiFrom4785(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑🏻‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: [.light]), + "🧑🏻‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: [.light]), + "🧑🏻‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: [.light]), + "🧑🏼‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: [.mediumLight]), + "🧑🏼‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: [.mediumLight]), + "🧑🏼‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: [.mediumLight]), + "🧑🏽‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: [.medium]), + "🧑🏽‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: [.medium]), + "🧑🏽‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: [.medium]), + "🧑🏾‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: [.mediumDark]), + "🧑🏾‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: [.mediumDark]), + "🧑🏾‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: [.mediumDark]), + "🧑🏿‍🦯‍➡️": EmojiWithSkinTones(baseEmoji: .personWithWhiteCaneFacingRight, skinTones: [.dark]), + "🧑🏿‍🦼‍➡️": EmojiWithSkinTones(baseEmoji: .personInMotorizedWheelchairFacingRight, skinTones: [.dark]), + "🧑🏿‍🦽‍➡️": EmojiWithSkinTones(baseEmoji: .personInManualWheelchairFacingRight, skinTones: [.dark]) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom4840(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👩‍❤️‍💋‍👨": EmojiWithSkinTones(baseEmoji: .womanKissMan, skinTones: nil), @@ -4409,6 +4655,13 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } + private static func emojiFrom5425(_ rawValue: String) -> EmojiWithSkinTones { + let lookup: [String: EmojiWithSkinTones] = [ + "🧑‍🧑‍🧒‍🧒": EmojiWithSkinTones(baseEmoji: .familyAdultAdultChildChild, skinTones: nil) + ] + return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) + } + private static func emojiFrom6037(_ rawValue: String) -> EmojiWithSkinTones { let lookup: [String: EmojiWithSkinTones] = [ "👩🏻‍❤️‍👨🏻": EmojiWithSkinTones(baseEmoji: .womanHeartMan, skinTones: [.light]), @@ -4729,4 +4982,3 @@ extension EmojiWithSkinTones { return (lookup[rawValue] ?? EmojiWithSkinTones(unsupportedValue: rawValue)) } } -// swiftlint:disable all diff --git a/Session/Home/App Review/AppReviewPromptModel.swift b/Session/Home/App Review/AppReviewPromptModel.swift index 9d9d82e2c8..d7c766e66c 100644 --- a/Session/Home/App Review/AppReviewPromptModel.swift +++ b/Session/Home/App Review/AppReviewPromptModel.swift @@ -17,8 +17,7 @@ struct AppReviewPromptModel { extension AppReviewPromptModel { // Base version where app review prompt became available - // TODO: Update this once a version to include app review prompt is decided - static private let reviewPromptAvailabilityVersion = "2.14.1" // stringlint:ignore + static private let reviewPromptAvailabilityVersion = "2.14.2" // stringlint:ignore /// Determines the initial state of the app review prompt. static func loadInitialAppReviewPromptState(using dependencies: Dependencies) -> AppReviewPromptState? { diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index 6ec7b202bb..98d29d990e 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -104,6 +104,8 @@ public class HomeViewModel: NavigatableStateHolder { public var observedKeys: Set { var result: Set = [ + .appLifecycle(.willEnterForeground), + .databaseLifecycle(.resumed), .loadPage(HomeViewModel.self), .messageRequestAccepted, .messageRequestDeleted, @@ -245,7 +247,7 @@ public class HomeViewModel: NavigatableStateHolder { } /// Handle database events first - if let databaseEvents: Set = splitEvents[.databaseQuery], !databaseEvents.isEmpty { + if !dependencies[singleton: .storage].isSuspended, let databaseEvents: Set = splitEvents[.databaseQuery], !databaseEvents.isEmpty { do { var fetchedConversations: [SessionThreadViewModel] = [] let idsNeedingRequery: Set = self.extractIdsNeedingRequery( @@ -354,6 +356,9 @@ public class HomeViewModel: NavigatableStateHolder { Log.critical(.homeViewModel, "Failed to fetch state for events [\(eventList)], due to error: \(error)") } } + else if let databaseEvents: Set = splitEvents[.databaseQuery], !databaseEvents.isEmpty { + Log.warn(.homeViewModel, "Ignored \(databaseEvents.count) database event(s) sent while storage was suspended.") + } /// Then handle non-database events let groupedOtherEvents: [GenericObservableKey: Set]? = splitEvents[.other]? @@ -445,6 +450,15 @@ public class HomeViewModel: NavigatableStateHolder { events: Set, cache: [String: SessionThreadViewModel] ) -> Set { + let requireFullRefresh: Bool = events.contains(where: { event in + event.key == .appLifecycle(.willEnterForeground) || + event.key == .databaseLifecycle(.resumed) + }) + + guard !requireFullRefresh else { + return Set(cache.keys) + } + return events.reduce(into: []) { result, event in switch (event.key.generic, event.value) { case (.conversationUpdated, let event as ConversationEvent): result.insert(event.id) @@ -741,6 +755,7 @@ private extension ObservedEvent { case (.feature(.forceOffline), _): return .other case (.setting(.hasViewedSeed), _): return .other + case (.appLifecycle(.willEnterForeground), _): return .databaseQuery case (.messageRequestUnreadMessageReceived, _), (.messageRequestAccepted, _), (.messageRequestDeleted, _), (.messageRequestMessageRead, _): return .databaseQuery diff --git a/Session/Home/New Conversation/NewMessageScreen.swift b/Session/Home/New Conversation/NewMessageScreen.swift index c478eb8331..4eb0dbfa9f 100644 --- a/Session/Home/New Conversation/NewMessageScreen.swift +++ b/Session/Home/New Conversation/NewMessageScreen.swift @@ -5,7 +5,7 @@ import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit struct NewMessageScreen: View { @EnvironmentObject var host: HostWrapper @@ -87,7 +87,7 @@ struct NewMessageScreen: View { // This could be an ONS name ModalActivityIndicatorViewController .present(fromViewController: self.host.controller?.navigationController!, canCancel: false) { modalActivityIndicator in - SnodeAPI + Network.SnodeAPI .getSessionID(for: accountIdOrONS, using: dependencies) .subscribe(on: DispatchQueue.global(qos: .userInitiated)) .receive(on: DispatchQueue.main) diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift index e3bf699035..81bf19c1c7 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift @@ -4,7 +4,7 @@ import UIKit import Combine import UniformTypeIdentifiers import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SignalUtilitiesKit import SessionUtilitiesKit diff --git a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift index 2e9b72936b..eb2cd33430 100644 --- a/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift +++ b/Session/Media Viewing & Editing/GIFs/GiphyAPI.swift @@ -5,7 +5,7 @@ import Combine import CoreServices import UniformTypeIdentifiers import SignalUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Singleton diff --git a/Session/Media Viewing & Editing/MediaPageViewController.swift b/Session/Media Viewing & Editing/MediaPageViewController.swift index 63bb6e82d7..28ab4d2426 100644 --- a/Session/Media Viewing & Editing/MediaPageViewController.swift +++ b/Session/Media Viewing & Editing/MediaPageViewController.swift @@ -6,7 +6,7 @@ import SessionUIKit import SessionMessagingKit import SignalUtilitiesKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, MediaDetailViewControllerDelegate, InteractivelyDismissableViewController { class DynamicallySizedView: UIView { diff --git a/Session/Media Viewing & Editing/MessageInfoScreen.swift b/Session/Media Viewing & Editing/MessageInfoScreen.swift index e614b81b5f..9456f8dc42 100644 --- a/Session/Media Viewing & Editing/MessageInfoScreen.swift +++ b/Session/Media Viewing & Editing/MessageInfoScreen.swift @@ -2,7 +2,7 @@ import SwiftUI import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import SessionMessagingKit @@ -183,7 +183,7 @@ struct MessageInfoScreen: View { spacing: Values.mediumSpacing ) { InfoBlock(title: "attachmentsFileId".localized()) { - Text(attachment.downloadUrl.map { Attachment.fileId(for: $0) } ?? "") + Text(attachment.downloadUrl.map { Network.FileServer.fileId(for: $0) } ?? "") .font(.system(size: Values.mediumFontSize)) .foregroundColor(themeColor: .textPrimary) } diff --git a/Session/Media Viewing & Editing/PhotoCapture.swift b/Session/Media Viewing & Editing/PhotoCapture.swift index d4e1bc8d2d..30b38d6a73 100644 --- a/Session/Media Viewing & Editing/PhotoCapture.swift +++ b/Session/Media Viewing & Editing/PhotoCapture.swift @@ -6,7 +6,7 @@ import Foundation import Combine import AVFoundation import CoreServices -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 9252c1016e..e70361c687 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -8,7 +8,7 @@ import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category @@ -70,7 +70,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD self.loadingViewController = LoadingViewController() AppSetup.setupEnvironment( - additionalMigrationTargets: [DeprecatedUIKitMigrationTarget.self], appSpecificBlock: { [dependencies] in Log.setup(with: Logger(primaryPrefix: "Session", using: dependencies)) Log.info(.cat, "Setting up environment.") @@ -222,7 +221,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // Dispatch async so things can continue to be progressed if a migration does need to run DispatchQueue.global(qos: .userInitiated).async { [weak self, dependencies] in AppSetup.runPostSetupMigrations( - additionalMigrationTargets: [DeprecatedUIKitMigrationTarget.self], migrationProgressChanged: { progress, minEstimatedTotalTime in self?.loadingViewController?.updateProgress( progress: progress, @@ -606,7 +604,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // The re-run the migration (should succeed since there is no data) AppSetup.runPostSetupMigrations( - additionalMigrationTargets: [DeprecatedUIKitMigrationTarget.self], migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in self?.loadingViewController?.updateProgress( progress: progress, @@ -707,7 +704,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD self?.startPollersIfNeeded() - SessionNetworkAPI.client.initialize(using: dependencies) + Network.SessionNetwork.client.initialize(using: dependencies) if dependencies[singleton: .appContext].isMainApp { DispatchQueue.main.async { diff --git a/Session/Meta/Session - Anonymous Messenger.storekit b/Session/Meta/Session - Anonymous Messenger.storekit new file mode 100644 index 0000000000..901eabe896 --- /dev/null +++ b/Session/Meta/Session - Anonymous Messenger.storekit @@ -0,0 +1,117 @@ +{ + "appPolicies" : { + "eula" : "", + "policies" : [ + { + "locale" : "en_US", + "policyText" : "", + "policyURL" : "" + } + ] + }, + "identifier" : "E83EE03B", + "nonRenewingSubscriptions" : [ + + ], + "products" : [ + + ], + "settings" : { + "_applicationInternalID" : "1470168868", + "_developerTeamID" : "SUQ8J2PCT7", + "_failTransactionsEnabled" : false, + "_lastSynchronizedDate" : 779753823.36554396, + "_locale" : "en_US", + "_storefront" : "USA", + "_storeKitErrors" : [ + { + "current" : null, + "enabled" : false, + "name" : "Load Products" + }, + { + "current" : null, + "enabled" : false, + "name" : "Purchase" + }, + { + "current" : null, + "enabled" : false, + "name" : "Verification" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Store Sync" + }, + { + "current" : null, + "enabled" : false, + "name" : "Subscription Status" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Transaction" + }, + { + "current" : null, + "enabled" : false, + "name" : "Manage Subscriptions Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Refund Request Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Offer Code Redeem Sheet" + } + ] + }, + "subscriptionGroups" : [ + { + "id" : "21752814", + "localizations" : [ + + ], + "name" : "Session Pro", + "subscriptions" : [ + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "0.99", + "familyShareable" : false, + "groupNumber" : 1, + "internalID" : "6749836944", + "introductoryOffer" : null, + "localizations" : [ + { + "description" : "Test 1 Week Session Pro Subscription", + "displayName" : "Test Session Pro", + "locale" : "en_US" + } + ], + "productID" : "com.getsession.org.pro_sub", + "recurringSubscriptionPeriod" : "P1W", + "referenceName" : "Session Pro Subscription", + "subscriptionGroupID" : "21752814", + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] + } + ] + } + ], + "version" : { + "major" : 4, + "minor" : 0 + } +} diff --git a/Session/Meta/Session+SNUIKit.swift b/Session/Meta/Session+SNUIKit.swift index 862f7a9eb3..2c7a2aedfe 100644 --- a/Session/Meta/Session+SNUIKit.swift +++ b/Session/Meta/Session+SNUIKit.swift @@ -3,7 +3,7 @@ import UIKit import AVFoundation import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - SessionSNUIKitConfig diff --git a/Session/Meta/Translations/Localizable.xcstrings b/Session/Meta/Translations/Localizable.xcstrings index 11c67635b7..e1b245a269 100644 --- a/Session/Meta/Translations/Localizable.xcstrings +++ b/Session/Meta/Translations/Localizable.xcstrings @@ -18625,6 +18625,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی پرۆمۆشنی ئەدمین" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی پرۆمۆشنی ئەدمین" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -18653,6 +18681,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sender adminforfremmelse" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sender adminforfremmelser" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -20924,11 +20980,71 @@ "appearanceAutoDarkMode" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avto-qaranlıq rejimi" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatický tmavý režim" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatischer Dunkler Modus" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Auto Dark Mode" } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mode sombre automatique" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatische nachtmodus" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mod întunecat automat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматический тёмный режим" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Automatisk mörkt läge" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otomatik karanlık tema" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматичний темний режим" + } } } }, @@ -28189,7 +28305,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Ícono de la app" + "value" : "Ícono de la Aplicación" } }, "es-ES" : { @@ -28246,6 +28362,18 @@ "value" : "앱 아이콘" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایکۆنی ئەپ" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایکۆنی ئەپ" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -28660,7 +28788,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "El ícono y nombre alternativos de la app se muestran en la pantalla principal y el cajón de aplicaciones." + "value" : "Ícono y nombre alternativos para la aplicación se muestran en la pantalla de inicio y en el menú de aplicaciones." } }, "es-ES" : { @@ -29137,7 +29265,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "El ícono alternativo de la app se muestra en la pantalla principal y en la biblioteca de apps. El nombre de la app seguirá apareciendo como \"{app_name}\"." + "value" : "El ícono alternativo para la aplicación se muestra en la pantalla de inicio y en el menú de aplicaciones. El nombre de la aplicación seguirá apareciendo como '{app_name}'." } }, "es-ES" : { @@ -29298,7 +29426,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Usar ícono alternativo de la app" + "value" : "Usar un ícono alternativo para la aplicación" } }, "es-ES" : { @@ -29471,7 +29599,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Usar ícono y nombre alternativos de la app" + "value" : "Usar un ícono y nombre alternativos para la aplicación" } }, "es-ES" : { @@ -29644,7 +29772,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccionar ícono alternativo de la app" + "value" : "Seleccione un ícono alternativo para la aplicación" } }, "es-ES" : { @@ -29695,6 +29823,18 @@ "value" : "대체 앱 아이콘을 선택" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایکۆن ئەپی جێگرەوە هەڵبژێرە" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایکۆن ئەپی جێگرەوە هەڵبژێرە" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -30157,7 +30297,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "MeetingSE" + "value" : "Eventos" } }, "es-ES" : { @@ -30959,11 +31099,41 @@ "appProBadge" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} nişanı" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odznak {app_pro}" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} Abzeichen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{app_pro} Badge" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} Badge" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Значок {app_pro}" + } } } }, @@ -63360,6 +63530,30 @@ "value" : "Mesaj göndərmək üçün bu kontaktı əngəldən çıxardın." } }, + "bal" : { + "stringUnit" : { + "state" : "translated", + "value" : "پیغام بھیجنے کے لئے اس رابطہ کو غیر بلاک کریں۔" + } + }, + "be" : { + "stringUnit" : { + "state" : "translated", + "value" : "Разблакуйце гэты кантакт, каб адправіць паведамленне" + } + }, + "bg" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отблокирай този контакт за да изпратиш съобщение" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "মেসেজ পাঠাতে এই কন্টাক্টটি আনব্লক করুন।" + } + }, "ca" : { "stringUnit" : { "state" : "translated", @@ -63378,6 +63572,12 @@ "value" : "Dadrwystro'r cyswllt hwn i anfon neges" } }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fjern blokering af denne kontakt for at sende en besked" + } + }, "de" : { "stringUnit" : { "state" : "translated", @@ -63396,6 +63596,12 @@ "value" : "Unblock this contact to send a message" } }, + "eo" : { + "stringUnit" : { + "state" : "translated", + "value" : "Malbloki tiun kontakton por sendi mesaĝon" + } + }, "es-419" : { "stringUnit" : { "state" : "translated", @@ -63408,30 +63614,84 @@ "value" : "Desbloquea este contacto para enviar mensajes." } }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sõnumi saatmiseks eemalda selle kontakti blokeering" + } + }, + "eu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kontaktu hau desblokeatu mezu bat bidaltzeko" + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "برای ارسال پیام،‌ ابتدا این مخاطب را از مسدود بودن درآورید!" + } + }, "fi" : { "stringUnit" : { "state" : "translated", "value" : "Lähettääksesi viestin tälle yhteystiedolle sinun tulee ensin poistaa asettamasi esto." } }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "I-unblock ang contact na ito para magpadala ng mensahe" + } + }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Débloquez ce contact pour envoyer un message" } }, + "gl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desbloquea este contacto para enviar unha mensaxe" + } + }, + "ha" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cire katanga wannan saduwa don aika saƙo" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "בטל חסימה של איש קשר זה כדי לשלוח הודעה" + } + }, "hi" : { "stringUnit" : { "state" : "translated", "value" : "कोई संदेश भेजने के लिए इस संपर्क को अनवरोधित करें" } }, + "hr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deblokiraj ovaj kontakt za slanje poruke" + } + }, "hu" : { "stringUnit" : { "state" : "translated", "value" : "Üzenet küldéséhez oldd fel a kontakt letiltását." } }, + "hy-AM" : { + "stringUnit" : { + "state" : "translated", + "value" : "Արգելաբացել այս կոնտակտը հաղորդագրություն ուղարկելու համար" + } + }, "id" : { "stringUnit" : { "state" : "translated", @@ -63456,6 +63716,18 @@ "value" : "შეტყობინების გაგზავნისთვის ბლოკი მოხსენით" } }, + "km" : { + "stringUnit" : { + "state" : "translated", + "value" : "ដោះការហាមឃាត់លេខទំនាក់ទំនងនេះ ដើម្បីផ្ញើសារ" + } + }, + "kn" : { + "stringUnit" : { + "state" : "translated", + "value" : "ಸಂದೇಶವೊಂದನ್ನು ಕಳುಹಿಸಲು ಈ ಸಂಪರ್ಕವನ್ನು ಬ್ಲಾಕ್ ಮಾಡಿ" + } + }, "ko" : { "stringUnit" : { "state" : "translated", @@ -63471,7 +63743,31 @@ "ku-TR" : { "stringUnit" : { "state" : "translated", - "value" : "ئەم پەیوەندە لابردن بۆ بریتیە لە ناردنی پەیامێک." + "value" : "Ji bo şandina peyamê vê bloka vî kontaktê rake" + } + }, + "lg" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sazaamu omukozesa kuno okusindika obubaka" + } + }, + "lt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atblokuokite šį kontaktą, kad išsiųstumėte žinutę" + } + }, + "lv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atbloķējiet šo kontaktu, lai nosūtītu ziņojumu" + } + }, + "mk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Одблокирај го овој контакт за да испратиш порака" } }, "mn" : { @@ -63486,6 +63782,12 @@ "value" : "Nyahsekat kontak ini untuk menghantar mesej" } }, + "my" : { + "stringUnit" : { + "state" : "translated", + "value" : "မက်ဆေ့ချ် ပို့ရန်အတွက်ဤဆက်သွယ်မှုသို့ ဘလော့ကိုဖြေပါ။" + } + }, "nb" : { "stringUnit" : { "state" : "translated", @@ -63498,6 +63800,12 @@ "value" : "Opphev blokkeringen på denne kontakten for å sende en melding." } }, + "ne-NP" : { + "stringUnit" : { + "state" : "translated", + "value" : "सन्देश पठाउन यो सम्पर्क अनब्लक गर्नुहोस्।" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -63507,7 +63815,19 @@ "nn-NO" : { "stringUnit" : { "state" : "translated", - "value" : "Opphev blokkeringen på denne kontakten for å sende en melding." + "value" : "Opphev blokkeringen på denne kontakten for å sende en melding" + } + }, + "ny" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pokankha Lamulo Llitsa lemba uthenga" + } + }, + "pa-IN" : { + "stringUnit" : { + "state" : "translated", + "value" : "ਸੁਨੇਹਾ ਭੇਜਣ ਲਈ ਇਸ ਸੰਪਰਕ ਨੂੰ ਅਨਬਲੌਕ ਕਰੋ।" } }, "pl" : { @@ -63516,6 +63836,12 @@ "value" : "Odblokuj ten kontakt, aby wysłać wiadomość" } }, + "ps" : { + "stringUnit" : { + "state" : "translated", + "value" : "د پیغام استولو لپاره له دې اړیکې بې بندیز وکړئ" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -63540,18 +63866,72 @@ "value" : "Разблокируйте этот контакт, чтобы отправить сообщение" } }, + "sh" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odblokirajte ovog kontakta da biste poslali poruku" + } + }, + "si-LK" : { + "stringUnit" : { + "state" : "translated", + "value" : "පණිවිඩය යැවීමට මෙම සබඳතාවය අනවහිර කරන්න" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pre odoslanie správy kontakt odblokujte" + } + }, + "sl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Za pošiljanje sporočila morate najprej odblokirati ta stik" + } + }, + "sq" : { + "stringUnit" : { + "state" : "translated", + "value" : "Që t’i dërgohet një mesazh, zhbllokojeni këtë kontakt" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Одблокирајте дописника да би послали поруку" + } + }, + "sr-Latn" : { + "stringUnit" : { + "state" : "translated", + "value" : "Одблокирајте дописника да би послали поруку" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Avblockera denna kontakt för att skicka meddelanden." } }, + "sw" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ondolea kizuizi kwa mawasiliano haya kutuma ujumbe" + } + }, "ta" : { "stringUnit" : { "state" : "translated", "value" : "ஒரு செய்தியை அனுப்ப இந்த தொடர்பை விடுவிக்கவும்." } }, + "te" : { + "stringUnit" : { + "state" : "translated", + "value" : "సందేశాన్ని పంపడానికి ఈ పరిచయాన్ని అనుమతించు" + } + }, "tr" : { "stringUnit" : { "state" : "translated", @@ -63564,6 +63944,24 @@ "value" : "Розблокувати контакт для надсилання повідомлення." } }, + "ur-IN" : { + "stringUnit" : { + "state" : "translated", + "value" : "پیغام بھیجنے کے لیے اس رابطے کو ان بلاک کریں" + } + }, + "uz" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xabar yuborish uchun ushbu kontaktni blokdan chiqaring" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mở khóa người (liên lạc) này để gởi thông báo" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -65030,11 +65428,53 @@ "blockedContactsManageDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Əngəllənmiş kontaktları görün və idarə edin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit a spravovat blokované kontakty." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blockierte Kontakte anzeigen und verwalten." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "View and manage blocked contacts." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bekijk en beheer geblokkeerde contacten." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Просматривайте и управляйте списком заблокированных контактов." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa och hantera blockerade kontakter." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Переглядайте та керуйте заблокованими контактами." + } } } }, @@ -78446,11 +78886,47 @@ "callsVoiceAndVideoModalDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Beta zənglərini istifadə edərkən IP-niz zəng tərəfdaşınıza və {session_foundation} serverinə görünür." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Funkce hlasových hovorů, která je nyní ve vývojové fázi (beta), odhalí vaši IP adresu těm, se kterými si voláte a také {session_foundation} serveru." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your IP is visible to your call partner and a {session_foundation} server while using beta calls." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uw IP is zichtbaar voor uw oproep partner en een {session_foundation} server tijdens het gebruik van bètagesprekken." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your IP is visible to your call partner and a {session_foundation} server while using beta calls." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Din IP är synlig för din samtalspartner och en {session_foundation}-server när du använder beta-samtal." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Під час здійснення бета-викликів Ваш IP може побачити співрозмовник та сервер {session_foundation}." + } } } }, @@ -83271,22 +83747,94 @@ "cancelPlan" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planı ləğv et" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zrušit tarif" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Cancel Plan" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abonnement annuleren" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Скасувати тарифний план" + } } } }, "change" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dəyişdir" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Změnit" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ändern" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Change" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wijzigen" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schimba" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изменить" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ändra" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Змінити" + } } } }, @@ -83772,11 +84320,80 @@ "changePasswordModalDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} üçün parolunuzu dəyişdirin. Daxili olaraq saxlanılmış verilər, yeni parolunuzla təkrar şifrələnəcək." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Změňte své heslo pro {app_name}. Lokálně uložená data budou znovu zašifrována pomocí vašeho nového hesla." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Change your password for {app_name}. Locally stored data will be re-encrypted with your new password." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wijzig je wachtwoord voor {app_name}. Lokaal opgeslagen gegevens worden opnieuw versleuteld met je nieuwe wachtwoord." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Измените пароль для {app_name}. Локально сохранённые данные будут повторно зашифрованы с использованием нового пароля." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ändra ditt lösenord för {app_name}. Lokalt lagrad data kommer att krypteras om med ditt nya lösenord." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Змінити ваш пароль для {app_name}. Локально збережені дані будуть наново шифровані з застосуванням нового паролю." + } + } + } + }, + "checkingProStatus" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Checking {pro} Status" + } + } + } + }, + "checkingProStatusDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Checking your {pro} details. Some information on this page may be inaccurate until this check is complete." + } + } + } + }, + "checkingProStatusUpgradeDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Checking your {pro} status. You'll be able to upgrade to {pro} once this check is complete." + } } } }, @@ -98407,12 +99024,24 @@ "value" : "Introdu o descriere a comunității" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Введите описание сообщества" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Ange en communitybeskrivning" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Введіть опис спільноти" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -105250,12 +105879,24 @@ "value" : "Introdu numele comunității" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Введите название сообщества" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Ange ett communitynamn" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Введіть назву спільноти" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -105363,12 +106004,24 @@ "value" : "Te rugăm să introduci un nume al comunității" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пожалуйста, введите название сообщества" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Vänligen ange ett communitynamn" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Будь ласка, введіть назву спільноти" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -113098,11 +113751,47 @@ "contentNotificationDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni bir mesaj alındıqda daxili bildirişlərdə nümayiş olunacaq məzmunu seçin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vyberte obsah, který se zobrazí v místních upozorněních při přijetí zprávy." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Choose the content displayed in local notifications when an incoming message is received." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kies de inhoud die wordt weergegeven in lokale meldingen wanneer een inkomend bericht wordt ontvangen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выберите содержимое, отображаемое в локальных уведомлениях при получении входящего сообщения." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Välj vilket innehåll som ska visas i lokala aviseringar när ett inkommande meddelande tas emot." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обирайте, який вміст показуватиметься у сповіщеннях після отримання повідомлення." + } } } }, @@ -118923,33 +119612,159 @@ "conversationsEnterDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter və Shift+Enter düymələrinin danışıqlarda necə işləyəcəyini təyin edin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definujte, jak budou fungovat klávesy Enter a Shift+Enter v konverzacích." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definiere, wie Eingabe- und Umschalttaste in Konversationen funktionieren." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Define how the Enter and Shift+Enter keys function in conversations." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stel in hoe de toetsen Enter en Shift+Enter functioneren in gesprekken." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Определите, как будут работать клавиши Enter и Shift+Enter в переписке." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definiera hur Enter- och Skift+Enter-tangenterna fungerar i konversationer." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштування дій клавіш Enter та Shift+Enter у розмовах." + } } } }, "conversationsEnterNewLine" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER mesajı göndərir, ENTER yeni sətrə keçir." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER odešle zprávu, ENTER začne nový řádek." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "SHIFT + ENTER sends a message, ENTER starts a new line." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER verzendt een bericht, ENTER begint een nieuwe regel." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER отправляет сообщение, ENTER начинает новую строку." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER skickar ett meddelande, ENTER startar en ny rad." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "SHIFT + ENTER надсилає повідомлення, ENTER починає новий рядок." + } } } }, "conversationsEnterSends" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER mesajı göndərir, SHIFT + ENTER yeni sətrə keçir." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zmáčknutím ENTER se zpráva odešle, SHIFT + ENTER vytvoří nový řádek." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "ENTER sends a message, SHIFT + ENTER starts a new line." } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER envía un mensaje, SHIFT + ENTER inicia una nueva línea." + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER envía un mensaje, SHIFT + ENTER inicia una nueva línea." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER verzendt een bericht, SHIFT + ENTER begint een nieuwe regel." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER для отправки, SHIFT + ENTER для новой строки." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER skickar ett meddelande, SHIFT + ENTER påbörjar en ny rad." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "ENTER надсилає повідомлення, SHIFT + ENTER починає новий рядок." + } } } }, @@ -120393,11 +121208,53 @@ "conversationsMessageTrimmingTrimCommunitiesDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "2,000-dən çox mesajı olan icmalarda 6 aydan köhnə mesajları avtomatik sil." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Z komunit automaticky mazat zprávy starší než 6 měsíců, pokud jich je více než 2000." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachrichten älter als 6 Monate in Communities mit mehr als 2000 Nachrichten automatisch löschen." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Auto-delete messages older than 6 months in communities with 2000+ messages." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Berichten ouder dan 6 maanden automatisch verwijderen in community's met meer dan 2000 berichten." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Удалять сообщения старше 6 месяцев в сообществах с более чем 2000 сообщений." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ta bort meddelanden som är äldre än 6 månader i gemenskaper med 2000+ meddelanden." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автовидалення повідомлень старших за 6 місяців у спільнотах з 2000+ повідомлень." + } } } }, @@ -121362,11 +122219,71 @@ "conversationsSendWithEnterKey" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter ilə göndər" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odeslat klávesou Enter" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mit Eingabetaste senden" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Send with Enter" } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter para enviar" + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter para enviar" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verzenden met Enter" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apasă Enter pentru a trimite" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отправлять по Enter" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skicka med Enter" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Надіслати з Enter" + } } } }, @@ -121852,11 +122769,53 @@ "conversationsSendWithShiftEnter" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shift+Enter ilə göndər" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odeslat klávesou Shift+Enter" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mit Umschalt- und Eingabetaste senden" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Send with Shift+Enter" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verzenden met Shift+Enter" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отправить с Shift+Enter" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skicka med Shift+Enter" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Надіслати з Shift+Enter" + } } } }, @@ -125413,22 +126372,76 @@ "currentPassword" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hazırkı parol" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktuální heslo" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Current Password" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Huidig wachtwoord" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nuvarande lösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поточний пароль" + } } } }, "currentPlan" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hazırkı plan" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Současný tarif" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Current Plan" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Huidig abonnement" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поточна передплата" + } } } }, @@ -125920,11 +126933,59 @@ "darkMode" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Qaranlıq rejim" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tmavý režim" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dunkelmodus" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Dark Mode" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Donkere modus" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mod întunecat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тёмный режим" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mörkt läge" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Темний режим" + } } } }, @@ -126164,6 +127225,12 @@ "value" : "데이터베이스 오류가 발생했습니다.

문제 해결을 위해 애플리케이션 로그를 내보내서 공유하십시오. 실패할 경우, {app_name}을 다시 설치하고 계정을 복원하십시오." } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "En databasefeil har oppstått.

Eksporter dine applikasjon logger for å dele feilsøkingen. Hvis dette ikke vellykkes, installer {app_name} på nytt og gjenopprett kontoen din." + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -139562,6 +140629,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "دڵنیایت دەتەوێت ئەم پەیامە بسڕیتەوە؟" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "دڵنیایت دەتەوێت ئەم پەیامانە بسڕیتەوە؟" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -139590,6 +140685,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er du sikker på at du vil slette denne meldingen?" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er du sikker på at du vil slette disse meldingene?" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -143672,6 +144795,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایا دڵنیای کە دەتەوێت ئەم پەیامە تەنها لەم ئامێرە بسڕیتەوە؟" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئایا دڵنیای کە دەتەوێت ئەم پەیامە تەنها لەم ئامێرە بسڕیتەوە؟" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -148756,6 +149907,34 @@ } } }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "پرشین" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "برخی از پیام‌هایی که انتخاب کرده‌اید، نمی‌توانند از همهٔ دستگاه‌های شما پاک شوند" + } + } + } + } + } + } + }, "fi" : { "stringUnit" : { "state" : "translated", @@ -148990,6 +150169,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئەم پەیامە ناتوانرێت لە هەموو ئامێرەکانت بسڕدرێتەوە" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "هەندێک لەو نامانەی کە هەڵتبژاردووە ناتوانرێت لە هەموو ئامێرەکانت بسڕدرێنەوە" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -149068,6 +150275,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Denne meldingen kan ikke bli slettet fra alle dine enheter" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Noen av meldingene du valgte kan ikke bli slettet fra alle dine enheter" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -150604,6 +151839,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "ئەم پەیامە بۆ هەموو کەسێک ناسڕدرێتەوە" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "هەندێک لەو نامانەی کە هەڵتبژاردووە ناتوانرێت بۆ هەموو کەسێک بسڕدرێتەوە" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -150696,13 +151959,13 @@ "one" : { "stringUnit" : { "state" : "translated", - "value" : "Diese Nachricht kann nicht gelöscht werden" + "value" : "Denne meldingen kan ikke bli slettet for alle" } }, "other" : { "stringUnit" : { "state" : "translated", - "value" : "Ein paar Nachrichten könnten nicht gelöscht werden" + "value" : "Noen av meldingene du valgte kan ikke bli slettet for alle" } } } @@ -168918,11 +170181,53 @@ "display" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nümayiş" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazení" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Display" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Display" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Weergave" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дисплей" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skärm" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Зовнішній вигляд" + } } } }, @@ -187186,11 +188491,53 @@ "enableNotifications" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni mesaj aldığınız zaman bildirişlər göstərilsin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit upozornění při přijetí nových zpráv." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Benachrichtigungen anzeigen, wenn du neue Nachrichten erhältst." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Show notifications when you receive new messages." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon meldingen wanneer je nieuwe berichten ontvangt." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показывать уведомления при получении новых сообщений." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa aviseringar när du får nya meddelanden." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показувати сповіщення, коли ви отримуєте нові повідомлення." + } } } }, @@ -187293,6 +188640,12 @@ "value" : "Gillar du {app_name}?" } }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name}'i beğendiniz mi?" + } + }, "uk" : { "stringUnit" : { "state" : "translated", @@ -187418,6 +188771,12 @@ "value" : "Behöver förbättras {emoji}" } }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geliştirilmesi Gerekiyor {emoji}" + } + }, "uk" : { "stringUnit" : { "state" : "translated", @@ -187543,6 +188902,12 @@ "value" : "Det är fantastiskt {emoji}" } }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Harika {emoji}" + } + }, "uk" : { "stringUnit" : { "state" : "translated", @@ -187691,11 +189056,64 @@ "enter" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daxil ol" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vstoupit" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bestätigen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Enter" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verder" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Войти" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Увійти" + } + } + } + }, + "errorCheckingProStatus" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error checking {pro} status." + } } } }, @@ -189297,6 +190715,17 @@ } } }, + "errorLoadingProPlan" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error loading {pro} plan." + } + } + } + }, "errorUnknown" : { "extractionState" : "manual", "localizations" : { @@ -190437,22 +191866,106 @@ "feedback" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Əks-əlaqə" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zpětná vazba" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feedback" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Feedback" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feedback" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отзыв" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feedback" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відгук" + } } } }, "feedbackDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Qısa anketi dolduraraq {app_name} ilə təcrübənizi paylaşın." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Podělte se o své zkušenosti s {app_name} vyplněním krátkého dotazníku." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Teile deine Erfahrungen mit {app_name}, indem du eine kurze Umfrage ausfüllst." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Share your experience with {app_name} by completing a short survey." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deel je ervaring met {app_name} door een korte enquête in te vullen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поделитесь своим опытом использования {app_name}, пройдя короткий опрос." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dela med dig av din upplevelse med {app_name} genom att fylla i en kort undersökning." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поділіться вашим досвідом використання {app_name} пройшовши коротке опитування." + } } } }, @@ -191417,11 +192930,59 @@ "followSystemSettings" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sistem ayarlarını izlə." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Použít nastavení systému." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Systemeinstellungen übernehmen." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Follow system settings." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Systeeminstellingen volgen." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Urmărește setările sistemului." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Использовать настройки системы." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Följ systeminställningen." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Використовувати системні налаштування." + } } } }, @@ -196276,6 +197837,12 @@ "value" : "정말로 {group_name}을 제거하시겠습니까?

모든 멤버가 제거되고 모든 그룹 컨텐츠가 삭제됩니다." } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er du sikker på at du vil slette {group_name}?

Dette vil fjerne alle medlemmer og slette alt av innholdet i gruppen." + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -203664,6 +205231,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی بانگ" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی بانگه کان" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -203692,6 +205287,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sender invitasjon" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sender invitasjoner" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -204638,6 +206261,12 @@ "value" : "초대 상태를 알 수 없습니다" } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status på invitasjonen er ukjent" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -214067,6 +215696,12 @@ "value" : "あなた{other_name} はグループに招待されました。チャット履歴が共有されました。" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "당신{other_name}이 그룹에 초대 되었습니다. 대화 내용이 공유 되었습니다." + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -231754,6 +233389,18 @@ "value" : "연결 후보 처리 중" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "مامەڵەکردن لەگەڵ کاندیدەکانی پەیوەندی" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "مامەڵەکردن لەگەڵ کاندیدەکانی پەیوەندی" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -232304,11 +233951,53 @@ "helpFAQDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ümumi suallara cavab tapmaq üçün {app_name} TVS-yə baxın." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odpovědi na časté otázky najdete v sekci FAQ {app_name}." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sieh dir die {app_name}-FAQ an, um Antworten auf häufig gestellte Fragen zu erhalten." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Check the {app_name} FAQ for answers to common questions." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bekijk de {app_name} FAQ voor antwoorden op veelgestelde vragen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ознакомьтесь с часто задаваемыми вопросами {app_name}, чтобы найти ответы на распространённые вопросы." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kolla in FAQ på {app_name} för svar på vanliga frågor." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Перегляд ЧЗП {app_name} для перегляду відповідей на часті запитання." + } } } }, @@ -232788,11 +234477,65 @@ "helpReportABug" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir xəta bildir" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nahlásit chybu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einen Fehler melden" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Report a Bug" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meld een bug" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Raportează o eroare" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сообщить об ошибке" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rapportera ett fel" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hata Bildir" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повідомити про помилку" + } } } }, @@ -234727,11 +236470,53 @@ "helpReportABugExportLogsSaveToDesktopDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu faylı saxlayın, sonra onu {app_name} gəlişdiriciləri ilə paylaşın." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uložte tento soubor, poté jej sdílejte s vývojáři aplikace {app_name}." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Speichere diese Datei und teile sie dann mit den {app_name} Entwicklern." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Save this file, then share it with {app_name} developers." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sla dit bestand op en deel het vervolgens met de {app_name} ontwikkelaars." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сохраните этот файл, затем поделитесь им с разработчиками {app_name}." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spara denna fil, dela den sedan med {app_name} utvecklarna." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Збережіть цей файл, а потім надішліть його розробникам {app_name}." + } } } }, @@ -235217,11 +237002,53 @@ "helpTranslateSessionDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} tətbiqini 80-dən çox dildə tərcümə etməyə kömək edin!" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pomozte přeložit {app_name} do více než 80 jazyků!" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hilf mit, {app_name} in über 80 Sprachen zu übersetzen!" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Help translate {app_name} into over 80 languages!" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Help met het vertalen van {app_name} in meer dan 80 talen!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помогите перевести {app_name} на более чем 80 языков!" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hjälp till att översätta {app_name} till över 80 språk!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Допоможіть перекласти {app_name} на більше ніж 80 мов!" + } } } }, @@ -236186,11 +238013,47 @@ "hideMenuBarDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sistem menyu çubuğunun görünməsini dəyişdir." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Přepínač viditelnosti lišty systémového menu." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Toggle system menu bar visibility." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zichtbaarheid systeem-menubalk in-/uitschakelen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Спрятать или показать системное меню." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Växla synlighet för systemmenyraden." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Видимість панелі меню." + } } } }, @@ -237477,11 +239340,35 @@ "important" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vacib" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Důležité" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Important" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Belangrijk" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Важливо" + } } } }, @@ -239874,6 +241761,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "بانگهێشتکردن شکستی هێنا" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "بانگهێشتەکان شکستی هێنا" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -239902,6 +241817,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invitasjon mislykket" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invitasjoner mislykket" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -240709,6 +242652,34 @@ } } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "بانگهێشتنامەکە نەتوانرا بنێردرێت. حەز دەکەیت هەوڵی دووبارە بدەیتەوە؟" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "بانگهێشتنامەکان نەتوانرا بنێردرێت. حەز دەکەیت هەوڵی دووبارە بدەیتەوە؟" + } + } + } + } + } + } + }, "ku-TR" : { "stringUnit" : { "state" : "translated", @@ -240737,6 +242708,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invitasjon kunne ikke bli sent. Vil du prøve på nytt?" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invitasjoner kunne ikke bli sent. Vil du prøve på nytt?" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -242032,10 +244031,22 @@ "launchOnStartDescriptionDesktop" : { "extractionState" : "manual", "localizations" : { + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spustit {app_name} automaticky při spuštění počítače." + } + }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Launch Session automatically when your computer starts up." + "value" : "Launch {app_name} automatically when your computer starts up." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автоматично запускати {app_name} під час увімкнення компʼютера." } } } @@ -242043,21 +244054,51 @@ "launchOnStartDesktop" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açılışda başlat" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spuštění při startu systému" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Launch on Startup" } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автозапуск при старті системи" + } } } }, "launchOnStartupDisabledDesktop" : { "extractionState" : "manual", "localizations" : { + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toto nastavení spravuje váš systém s Linuxem. Chcete-li povolit automatické spuštění, přidejte {app_name} do spouštěných aplikací v nastavení systému." + } + }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "This setting is managed by your system on Linux. To enable automatic startup, add Session to your startup applications in system settings." + "value" : "This setting is managed by your system on Linux. To enable automatic startup, add {app_name} to your startup applications in system settings." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Цим параметром у Linux керує ваша система. Щоб увімкнути автоматичний запуск, додайте {app_name} до програм автозапуску в системних параметрах." } } } @@ -244245,7 +246286,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "El historial de chat no se transferirá al nuevo grupo. Todavía puedes ver todo el historial de chat en tu grupo antiguo." + "value" : "El historial de chat no se transferirá al nuevo grupo. Aún puedes ver todo el historial de chat en tu antiguo grupo." } }, "es-ES" : { @@ -252019,11 +254060,41 @@ "links" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keçidlər" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odkazy" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Links" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Koppelingen" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Länkar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Посилання" + } } } }, @@ -257790,11 +259861,41 @@ "logs" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Log-lar" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logy" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Logs" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logboeken" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loggar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Журнали" + } } } }, @@ -257962,11 +260063,35 @@ "managePro" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} - idarə et" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spravovat {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Manage {pro}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} beheren" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштування {pro}" + } } } }, @@ -268192,11 +270317,53 @@ "menuBar" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menyu çubuğu" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Panel menu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menüleiste" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Menu Bar" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menubalk" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Панель меню" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menyrad" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Панель меню" + } } } }, @@ -268819,11 +270986,53 @@ "messageCopy" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mesajı kopyala" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kopírovat zprávu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachricht kopieren" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Copy Message" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bericht kopiëren" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Копировать текст сообщения" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kopiera meddelande" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Копіювати повідомлення" + } } } }, @@ -277802,6 +280011,24 @@ } } }, + "nb" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Du har fått en ny melding i {group_name}." + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Du har fått %lld nye meldinger i {group_name}." + } + } + } + } + }, "nl" : { "variations" : { "plural" : { @@ -292104,6 +294331,24 @@ } } }, + "nb" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meldinger har en tegngrense på {limit} tegn. Du har %lld tegn igjen." + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meldinger har en tegngrense på {limit} tegn. Du har %lld tegn igjen." + } + } + } + } + }, "nl" : { "variations" : { "plural" : { @@ -293004,11 +295249,41 @@ "newPassword" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni parol" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nové heslo" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "New Password" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nieuw wachtwoord" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nytt Lösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Новий пароль" + } } } }, @@ -293494,11 +295769,35 @@ "nextSteps" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Növbəti addımlar" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Další kroky" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Next Steps" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Volgende stappen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подальші кроки" + } } } }, @@ -299001,11 +301300,53 @@ "notificationDisplay" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bildiriş nümayişi" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazení upozornění" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Notification Display" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notificatie weergave" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vizualizare notificări" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отображение уведомлений" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aviseringsvisning" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сповіщення" + } } } }, @@ -301892,22 +304233,100 @@ "notificationSenderNameAndPreview" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mesajın göndərənin adı və mesaj məzmununun bir önizləməsi nümayiş olunsun." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit jméno odesílatele a náhled obsahu zprávy." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeigt den Namen des Absenders und eine Vorschau des Nachrichteninhalts an." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Display the sender's name and a preview of the message content." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon de naam van de afzender en een voorbeeld van de berichtinhoud." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показывать имя отправителя и предварительный просмотр содержимого сообщения." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa avsändarens namn och en förhandsvisning av meddelandets innehåll." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показувати ім’я відправника та стислий вміст повідомлення." + } } } }, "notificationSenderNameOnly" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heç bir mesaj məzmunu olmadan yalnız mesajı göndərənin adı nümayiş olunsun." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit pouze jméno odesílatele bez obsahu zprávy." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Display only the sender's name without any message content." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon alleen de naam van de afzender zonder enige berichtinhoud." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отображать только имя отправителя без содержимого сообщения." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa endast avsändarens namn utan något meddelandeinnehåll." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показувати лише ім'я відправника без вмісту повідомлення." + } } } }, @@ -303506,11 +305925,53 @@ "notificationsGenericOnly" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mesajı göndərənin adı və ya mesajın məzmunu olmadan ümumi {app_name} bildirişi nümayiş olunsun." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit obecné oznámení {app_name} bez jména odesílatele a obsahu zprávy." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeige eine allgemeine {app_name}-Benachrichtigung ohne Namen des Absenders oder Nachrichteninhalt an." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Display a generic {app_name} notification without the sender's name or message content." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon een algemene {app_name} melding zonder de naam van de afzender of de inhoud van het bericht." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показывать общие уведомления {app_name} без имени отправителя и содержимого сообщения." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa en generell {app_name}-avisering utan avsändarens namn eller meddelandets innehåll." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показувати типове сповіщення {app_name} без імені відправника або вмісту повідомлення." + } } } }, @@ -306840,11 +309301,53 @@ "notificationsMakeSound" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni mesaj aldığınız zaman bir səs oxudulsun." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Přehrát zvuk při přijetí nových zpráv." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einen Ton abspielen, wenn neue Nachrichten empfangen werden." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Play a sound when you receive receive new messages." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Speel een geluid af wanneer je nieuwe berichten ontvangt." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Воспроизводить звук при получении новых сообщений." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spela upp ett ljud när du får nya meddelanden." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відтворювати звук, коли ви отримуєте нові повідомлення." + } } } }, @@ -323950,22 +326453,58 @@ "onDevice" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{device_type} cihazınızda" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Na vašem zařízení {device_type}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "On your {device_type} device" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Op je {device_type} apparaat" + } } } }, "onDeviceDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başda qeydiyyatdan keçdiyiniz {platform_account} hesabına giriş etdiyiniz {device_type} cihazından bu {app_name} hesabını açın. Sonra planınızı {app_pro} ayarları vasitəsilə dəyişdirin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevřete tento účet {app_name} na zařízení {device_type}, které je přihlášeno do účtu {platform_account}, pomocí kterého jste se původně zaregistrovali. Poté změňte svůj tarif v nastavení {app_pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Open this {app_name} account on an {device_type} device logged into the {platform_account} you originally signed up with. Then, change your plan via the {app_pro} settings." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open dit {app_name} account op een {device_type} apparaat waarop je bent aangemeld met het {platform_account} waarmee je je oorspronkelijk hebt geregistreerd. Wijzig vervolgens je abonnement via de instellingen van {app_pro}." + } } } }, @@ -328301,11 +330840,29 @@ "openStoreWebsite" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_store} veb saytını aç" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otevřít webovou stránku {platform_store}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Open {platform_store} Website" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open de {platform_store} website" + } } } }, @@ -328916,11 +331473,59 @@ "password" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parol" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heslo" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwort" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Password" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoord" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parolă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль" + } } } }, @@ -329412,22 +332017,124 @@ "passwordChangedDescriptionToast" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parolunuz dəyişdirilib. Lütfən onu güvəndə saxlayın." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your password has been changed. Please keep it safe." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dein Passwort wurde geändert. Bitte bewahre es sicher auf." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your password has been changed. Please keep it safe." } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tu contraseña ha sido cambiada. Por favor, guárdala en un lugar seguro." + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tu contraseña ha sido cambiada. Por favor, guárdala en un lugar seguro." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uw wachtwoord is gewijzigd. Hou het veilig." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parola ta a fost modificata. Securizați-va parola." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль был изменен. Пожалуйста, храните его в безопасном месте." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt lösenord har ändrats. Håll det säkert." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль змінено. Будь ласка, зберігайте його надійно." + } } } }, "passwordChangeShortDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} kilidini açmaq üçün tələb olunan parolu dəyişdir." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Změnit heslo pro odemykání {app_name}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Change the password required to unlock {app_name}." } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obligation de changer le mot de passe pour déverrouiller {app_name}." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wijzig het wachtwoord dat nodig is om {app_name} te ontgrendelen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Измените пароль, необходимый для разблокировки {app_name}." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ändra lösenordet som krävs att låsa upp {app_name}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Змінити пароль, необхідний для розблокування {app_name}." + } } } }, @@ -329919,11 +332626,65 @@ "passwordCreate" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parol yarat" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vytvořit heslo" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwort erstellen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Create Password" } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créer un mot de passe" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoord aanmaken" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Создать пароль" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skapa lösenord" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Şifre Oluştur" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Створити пароль" + } } } }, @@ -332415,12 +335176,24 @@ "value" : "Parola trebuie să aibă între {min} și {max} caractere." } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль должен содержать от {min} до {max} символов" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Lösenordet måste vara mellan {min} och {max} tecken långt" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль має містити від {min} до {max} символів" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -333875,11 +336648,59 @@ "passwordNewConfirm" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yeni parolu təsdiqlə" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Potvrďte nové heslo" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neues Passwort wiederholen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Confirm New Password" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bevestig nieuwe wachtwoord" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirmați noua parolă" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подтвердите новый пароль" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bekräfta nytt lösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Підтвердити новий пароль" + } } } }, @@ -334365,33 +337186,165 @@ "passwordRemovedDescriptionToast" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parolunuz silinib." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaše heslo bylo odebráno." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dein Passwort wurde entfernt." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your password has been removed." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uw wachtwoord is verwijderd." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parola ta a fost ștearsă." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль удалён." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt lösenord har tagits bort." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль видалено." + } } } }, "passwordRemoveShortDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} kilidini açmaq üçün tələb olunan parolu sil" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odebrat heslo pro odemykání {app_name}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Remove the password required to unlock {app_name}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwijder het wachtwoord dat nodig is om {app_name} te ontgrendelen" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Удалить пароль, необходимый для разблокировки {app_name}" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ta bort lösenordet som krävs för att låsa upp {app_name}" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Видалити пароль, потрібний для розблокування {app_name}" + } } } }, "passwords" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parollar" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hesla" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwörter" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Passwords" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoorden" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parole" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароли" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Паролі" + } } } }, @@ -334877,99 +337830,483 @@ "passwordSetDescriptionToast" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parolunuz təyin edilib. Lütfən onu güvəndə saxlayın." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaše heslo bylo nastaveno. Pečlivě si jej uložte." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dein Passwort wurde festgelegt. Bitte bewahre es sicher auf." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your password has been set. Please keep it safe." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uw wachtwoord is ingesteld. Hou het veilig." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parola ta a fost setata. Securizați-va parola." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль установлен. Пожалуйста, храните его в безопасном месте." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt lösenord har angetts. Håll det säkert." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль встановлено. Будь ласка, зберігайте його надійно." + } } } }, "passwordSetShortDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açılışda {app_name} kilidini açmaq üçün parol tələb edilsin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vyžadovat heslo k odemknutí {app_name} při spuštění." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Require password to unlock {app_name} on startup." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoord vereisen om {app_name} bij het opstarten te ontgrendelen." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Требовать пароль для разблокировки {app_name} при запуске." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kräv ett lösenord för ett låsa upp {app_name} vid start." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вимагати пароль для розблокування {app_name} при вході." + } } } }, "passwordStrengthCharLength" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "12 xarakterdən uzun" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delší než 12 znaků" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Länger als 12 Zeichen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Longer than 12 characters" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Langer dan 12 tekens" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mai mare de 12 caractere" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Длина больше 12 символов" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Längre än 12 tecken" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Довший 12 символів" + } } } }, "passwordStrengthIncludeNumber" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir rəqəm ehtiva etməlidir" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obsahuje číslici" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enthält eine Zahl" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Includes a number" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bevat een cijfer" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Include un număr" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Содержит цифру" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inkluderar en siffra" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Містить цифру" + } } } }, "passwordStrengthIncludesLowercase" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir kiçik hərf ehtiva etməlidir" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obsahuje malé písmeno" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enthält einen Kleinbuchstaben" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Includes a lowercase letter" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bevat een kleine letter" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Include o literă mică" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Содержит строчную букву" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inkluderar en liten bokstav" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Містить літеру нижнього регістру" + } } } }, "passwordStrengthIncludesSymbol" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir simvol daxildir" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obsahuje symbol" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Includes a symbol" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bevat een symbool" + } } } }, "passwordStrengthIncludesUppercase" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bir böyük hərf ehtiva etməlidir" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obsahuje velké písmeno" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enthält einen Großbuchstaben" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Includes a uppercase letter" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bevat een hoofdletter" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Include o literă mare" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Содержит заглавную букву" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inkluderar en stor bokstav" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Містить літеру верхнього регістру" + } } } }, "passwordStrengthIndicator" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parol gücü göstəricisi" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Indikátor síly hesla" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwortstärke-Anzeige" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Password Strength Indicator" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wachtwoordsterkte indicator" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Indicator de parolă puternică" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Индикатор надёжности пароля" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Indikator för lösenordsstyrka" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Індикатор надійності паролю" + } } } }, "passwordStrengthIndicatorDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güclü bir parol təyin etmək, cihazınız itsə və ya oğurlansa belə mesajlarınızı və qoşmalarınızı qorumağa kömək edir." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastavení silného hesla pomáhá chránit vaše zprávy a přílohy v případě ztráty nebo odcizení zařízení." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Setting a strong password helps protect your messages and attachments if your device is ever lost or stolen." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Een sterk wachtwoord helpt je berichten en bijlagen te beschermen als je apparaat ooit verloren raakt of wordt gestolen." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Setarea unei parole puternice ajută la protejarea mesajelor și fișierelor în cazul pierderii sau furtului dispozitivului dumneavoastră." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Надёжный пароль помогает защитить ваши сообщения и вложения в случае утери или кражи устройства." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Att skapa ett starkt lösenord hjälper till att skydda dina meddelanden och bilagor om din enhet skulle gå förlorad eller bli stulen." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Встановлення надійного пароля допомагає захистити ваші повідомлення та вкладення у разі втрати або крадіжки пристрою." + } } } }, @@ -335506,7 +338843,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Cambio de permiso" + "value" : "Cambiar Permisos" } }, "es-ES" : { @@ -339458,11 +342795,71 @@ "permissionsKeepInSystemTrayDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pəncərəni bağladığınız zaman {app_name} arxaplanda çalışmağa davam edir." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} pokračuje v běhu na pozadí, když zavřete okno." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} läuft im Hintergrund weiter, wenn du das Fenster schließt." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{app_name} continues running in the background when you close the window." } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} continue à fonctionner en arrière-plan lorsque vous fermez la fenêtre." + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} fortsetter å kjøre i bakgrunnen når du lukker vinduet." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} blijft op de achtergrond draaien wanneer je het venster sluit." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} продолжит работать в фоновом режиме даже после закрытия окна." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} fortsätter köras i bakgrunden när du stänger fönstret." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} pencereyi kapattığınızda arka planda çalışmaya devam eder." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} продовжує працювати у фоновому режимі, коли ви його згортає." + } } } }, @@ -339993,7 +343390,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Se requiere acceso a la red local para facilitar las llamadas. Activa el permiso de \"Red local\" en Configuración para continuar." + "value" : "Se requiere acceso a la Red Local para realizar llamadas. En Configuración, active el permiso de \"Red Local\" para continuar." } }, "es-ES" : { @@ -340464,7 +343861,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Permitir acceso a la red local para facilitar llamadas de voz y video." + "value" : "Permitir acceso a la red local para realizar llamadas de voz y video." } }, "es-ES" : { @@ -340625,7 +344022,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Red local" + "value" : "Red Local" } }, "es-ES" : { @@ -349013,33 +352410,141 @@ "plusLoadsMore" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Üstəgəl daha çoxu gəlir..." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plus načte další..." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Plus Loads More..." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plus laad meer..." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Та багато іншого..." + } } } }, "plusLoadsMoreDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} üçün yeni özəlliklər tezliklə gəlir. {icon} {pro} Yol Xəritəsində yenilikləri kəşf edin" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nové funkce {pro} již brzy. Podívejte se, co chystáme, na plánu vývoje {pro} {icon}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "New features coming soon to {pro}. Discover what's next on the {pro} Roadmap {icon}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nieuwe functies komen binnenkort naar {pro}. Ontdek wat er komt op de {pro} Roadmap {icon}" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нові можливості незабаром виникнуть у {pro}. Пізнай, що буде далі, у дороговказі {pro} {icon}" + } } } }, "preferences" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tərcihlər" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Předvolby" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einstellungen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Preferences" } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preferencias" + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preferencias" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voorkeuren" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preferințe" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Предпочтения" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inställningar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштування" + } } } }, @@ -349525,11 +353030,53 @@ "previewNotification" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bildirişi önizlə" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Náhled upozornění" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Benachrichtigungsvorschau" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Preview Notification" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voorbeeldmelding" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Предпросмотр уведомления" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Förhandsgranska avisering" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Попередній перегляд сповіщень" + } } } }, @@ -349661,22 +353208,70 @@ "proAllSet" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hər şey hazırdır!" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vše je nastaveno!" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "You're all set!" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alles is geregeld!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Готово!" + } } } }, "proAllSetDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınız güncəlləndi! Hazırkı {pro} planınız avtomatik olaraq {date} tarixində yeniləndiyi zaman ödəniş haqqı alınacaq." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} byl aktualizován! Účtování proběhne při automatickém obnovení vašeho aktuálního tarifu {pro} dne {date}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan was updated! You will be billed when your current {pro} plan is automatically renewed on {date}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement is bijgewerkt! Je wordt gefactureerd wanneer je huidige {pro} abonnement automatisch wordt verlengd op {date}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Твою підписку {app_pro} оновлено. {date} коли підписку {pro} буде подовжено, тоді й стягнуть гроші." + } } } }, @@ -350166,12 +353761,24 @@ "value" : "Poză de profil animată" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Анимированное изображение профиля" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Animerad visningsbild" } }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil Onur Seçin" + } + }, "uk" : { "stringUnit" : { "state" : "translated", @@ -350326,22 +353933,70 @@ "proAnimatedDisplayPictures" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Animasiyalı ekran şəkilləri" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Animované zobrazované obrázky" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Animated Display Pictures" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geanimeerde profielfoto's" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Анімовані зображення облікового запису" + } } } }, "proAnimatedDisplayPicturesDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Animasiyalı GIF və WebP təsvirlərini ekran şəklini olaraq təyin edin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastavte si animované obrázky GIF a WebP jako svůj zobrazovaný profilový obrázek." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Set animated GIFs and WebP images as your display picture." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stel geanimeerde GIF's en WebP-afbeeldingen in als je profielfoto." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Встановлювати анімовані зображення GIF та WebP як ваше зображення облікового запису." + } } } }, @@ -350479,50 +354134,214 @@ "proAutoRenewTime" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro}, {time} tarixində avto-yenilənir" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} se automaticky obnoví za {time}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} auto-renewing in {time}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} wordt automatisch verlengd over {time}" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} автоматично оновиться за {time}" + } } } }, "proBadge" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} nişanı" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odznak {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} Badge" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} badge" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Значок {pro}" + } } } }, "proBadges" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nişanlar" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odznaky" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Badges" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Badges" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Позначки" + } } } }, "proBadgesDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekran adınızın yanında eksklüziv bir nişanla {app_name} tətbiqini dəstəklədiyinizi göstərin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vyjádřete svou podporu {app_name} pomocí exkluzivního odznaku vedle svého zobrazovaného jména." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Show your support for {app_name} with an exclusive badge next to your display name." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon je steun voor {app_name} met een exclusieve badge naast je schermnaam." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Продемонструйте свою підтримку {app_name} з ексклюзивним значком поруч з власним іменем." + } } } }, "proBadgesSent" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} nişan göndərildi" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} nişan göndərildi" + } + } + } + } + } + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} odznaky odeslány" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} odznaků odesláno" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} odznak odeslán" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} odznaků odesláno" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -350550,50 +354369,174 @@ } } } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} Merke Sendt" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} {pro} Merker Sendt" + } + } + } + } + } + } } } }, "proBadgeVisible" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} nişanını digər istifadəçilərə göstər" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit odznak {app_pro} ostatním uživatelům" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Show {app_pro} badge to other users" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toon het {app_pro} badge aan andere gebruikers" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показувати значок {app_pro} іншим користувачам" + } } } }, "proBilledAnnually" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} - illik haqq" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} účtováno ročně" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{price} Billed Annually" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} Jaarlijks gefactureerd" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} сплата щорічно" + } } } }, "proBilledMonthly" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} - aylıq haqq" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} účtováno měsíčně" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{price} Billed Monthly" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} Maandelijks gefactureerd" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} сплата щомісячно" + } } } }, "proBilledQuarterly" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} - rüblük haqq" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} účtováno čtvrtletně" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{price} Billed Quarterly" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} per kwartaal gefactureerd" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{price} сплата щоквартально" + } } } }, @@ -350999,99 +354942,314 @@ "processingRefundRequest" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} geri ödəmə tələbinizi emal edir" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} zpracovává vaši žádost o vrácení peněz" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{platform_account} is processing your refund request" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} verwerkt je restitutieverzoek" + } } } }, "proDiscountTooltip" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hazırkı planınızda artıq tam {app_pro} qiymətinin {percent}% endirimi mövcuddur." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš aktuální tarif je již zlevněn o {percent} % z plné ceny {app_pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your current plan is already discounted by {percent}% of the full {app_pro} price." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je huidige abonnement is al met {percent}% korting ten opzichte van de volledige {app_pro} prijs." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "На поточну підписку ти вже маєш знижку {percent}% від загальної ціни {app_pro}." + } + } + } + }, + "proErrorRefreshingStatus" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error refreshing {pro} status" + } } } }, "proExpired" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Müddəti bitib" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Platnost vypršela" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Expired" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verlopen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Підписка сплила" + } } } }, "proExpiredDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Təəssüf ki, {pro} planınızın müddəti bitib. {app_pro} tətbiqinin eksklüziv imtiyazlarına və özəlliklərinə erişimi davam etdirmək üçün yeniləyin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bohužel, váš tarif {pro} vypršel. Obnovte jej, abyste nadále měli přístup k exkluzivním výhodám a funkcím {app_pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Unfortunately, your {pro} plan has expired. Renew to keep accessing the exclusive perks and features of {app_pro}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Helaas is je {pro} abonnement verlopen. Verleng om toegang te blijven houden tot de exclusieve voordelen en functies van {app_pro}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "На жаль, підписка {pro} сплила. Онови її задля збереження переваг і можливостей {app_pro}." + } } } }, "proExpiringSoon" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tezliklə bitir" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Brzy vyprší" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Expiring Soon" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verloopt binnenkort" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Невдовзі спливе підписка" + } } } }, "proExpiringSoonDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planınızın müddəti {time} vaxtında bitir. {app_pro} tətbiqinin eksklüziv imtiyazlarına və özəlliklərinə erişimi davam etdirmək üçün planınızı güncəlləyin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {pro} vyprší za {time}. Aktualizujte si tarif, abyste i nadále měli přístup k exkluzivním výhodám a funkcím {app_pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {pro} plan is expiring in {time}. Update your plan to keep accessing the exclusive perks and features of {app_pro}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {pro} abonnement verloopt over {time}. Werk je abonnement bij om toegang te blijven houden tot de exclusieve voordelen en functies van {app_pro}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Підписка {pro} спливе {time}. Онови підписку задля збереження переваг і можливостей {app_pro}." + } } } }, "proExpiringTime" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro}, {time} vaxtında başa çatır" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} vyprší za {time}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} expiring in {time}" } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} спливає за {time}" + } } } }, "proFaq" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} TVS" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} FAQ" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} FAQ" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} FAQ" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} ЧАП" + } } } }, "proFaqDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} TVS-da tez-tez verilən suallara cavab tapın." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Najděte odpovědi na časté dotazy v nápovědě {app_name}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Find answers to common questions in the {app_name} FAQ." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vind antwoorden op veelgestelde vragen in de {app_name} FAQ." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відповіді на загальні запитання знайдеш у ЧаПи {app_name}." + } } } }, @@ -351765,11 +355923,35 @@ "proFeatures" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} özəllikləri" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Funkce {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} Features" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} functies" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Можливості {pro}" + } } } }, @@ -354746,6 +358928,12 @@ "value" : "Grup activat" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Группа активирована" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", @@ -354865,6 +359053,12 @@ "value" : "Acest grup are capacitate extinsă! Poate susține până la 300 de membri deoarece un administrator de grup are" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "У этой группы увеличена вместимость! Теперь она поддерживает до 300 участников, потому что администратор группы активировал" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", @@ -354894,6 +359088,74 @@ "proGroupsUpgraded" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} qrup yüksəldildi" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} qrup yüksəldildi" + } + } + } + } + } + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} skupiny navýšeny" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} skupin navýšeno" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} skupina navýšena" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} skupin navýšeno" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -354921,17 +359183,69 @@ } } } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Gruppe Oppgradert" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Grupper Oppgradert" + } + } + } + } + } + } } } }, "proImportantDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri ödəniş tələbi qətidir. Əgər təsdiqlənsə, {pro} planınız dərhal ləğv ediləcək və bütün {pro} özəlliklərinə erişimi itirəcəksiniz." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádost o vrácení peněz je konečné. Pokud bude schváleno, váš tarif {pro} bude ihned zrušen a ztratíte přístup ke všem funkcím {pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Requesting a refund is final. If approved, your {pro} plan will be canceled immediately and you will lose access to all {pro} features." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Het aanvragen van een terugbetaling is definitief. Indien goedgekeurd, wordt je {pro} abonnement onmiddellijk geannuleerd en verlies je de toegang tot alle {pro} functies." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вимагання повернення грошей закінчено. В разі схвалення твою підписку {pro} негайно скасують і ти втратиш всі можливості {pro}." + } } } }, @@ -355028,6 +359342,12 @@ "value" : "Dimensiune mărită a atașamentului" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Увеличенный размер вложений" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", @@ -355147,6 +359467,12 @@ "value" : "Lungime extinsă a mesajului" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Увеличенная длина сообщения" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", @@ -355176,50 +359502,214 @@ "proLargerGroups" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daha böyük qruplar" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Větší skupiny" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Larger Groups" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Grotere groepen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Більші групи" + } } } }, "proLargerGroupsDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin olduğunuz qruplar, avtomatik olaraq 300 üzvü dəstəkləmək üçün təkmilləşdirilir." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skupiny, ve kterých jste správcem, jsou automaticky navýšeny na kapacitu až 300 členů." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Groups you are an admin in are automatically upgraded to support 300 members." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Groepen waarvan jij beheerder bent, worden automatisch geüpgraded om 300 leden te ondersteunen." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Групи, у яких ви є адміністратором, автоматично оновлюються для підтримки до 300 учасників." + } } } }, "proLongerMessages" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daha uzun mesajlar" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delší zprávy" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Longer Messages" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Langere berichten" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Довші повідомлення" + } } } }, "proLongerMessagesDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bütün danışıqlarda 10,000 xarakterə qədər mesaj göndərə bilərsiniz." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ve všech konverzacích můžete posílat zprávy až o délce 10 000 znaků." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "You can send messages up to 10,000 characters in all conversations." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je kunt berichten tot 10.000 tekens verzenden in alle gesprekken." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ви можете надсилати повідомлення до 10 000 символів у всіх розмовах." + } } } }, "proLongerMessagesSent" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} daha uzun mesaj göndərildi" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} daha uzun mesaj göndərildi" + } + } + } + } + } + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} delší zprávy odeslány" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} delších zpráv odesláno" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} delší zpráva odeslána" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} delších zpráv odesláno" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -355247,6 +359737,34 @@ } } } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Lengre Melding Sendt" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Lengre Meldinger Sendt" + } + } + } + } + } + } } } }, @@ -355343,6 +359861,12 @@ "value" : "Acest mesaj a folosit următoarele funcționalități {app_pro}:" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Это сообщение использовало следующие функции {app_pro}:" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", @@ -356321,6 +360845,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forfremmelse mislykket" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forfremmelser mislykket" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -357128,6 +361680,34 @@ } } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forfremmelsen kunne ikke bli påført. Vil du prøve på nytt?" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forfremmelser kunne ikke bli påført. Vil du prøve på nytt?" + } + } + } + } + } + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -357465,17 +362045,103 @@ "proPercentOff" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{percent}% endirim" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sleva {percent} %" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{percent}% Off" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{percent}% korting" + } } } }, "proPinnedConversations" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} sancılmış danışıq" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} sancılmış danışıq" + } + } + } + } + } + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} připnuté konverzace" + } + }, + "many" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} připnutých konverzací" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} připnutá konverzace" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} připnutých konverzací" + } + } + } + } + } + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -357503,149 +362169,508 @@ } } } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "%#@arg1@" + }, + "substitutions" : { + "arg1" : { + "argNum" : 1, + "formatSpecifier" : "lld", + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Samtale Festet" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "{total} Samtaler Festet" + } + } + } + } + } + } } } }, "proPlanActivatedAuto" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınız aktivdir!

Planınız avtomatik olaraq {date} tarixində başqa bir {current_plan} üçün yenilənəcək. Planınıza edilən güncəlləmələr növbəti {pro} yenilənməsi zamanı qüvvəyə minəcək." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} je aktivní!

Váš tarif se automaticky obnoví na další {current_plan} dne {date}. Změny tarifu se projeví při příštím obnovení {pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan is active!

Your plan will automatically renew for another {current_plan} on {date}. Updates to your plan take effect when {pro} is next renewed." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement is actief!

Je abonnement wordt automatisch verlengd voor een nieuw {current_plan} op {date}. Wijzigingen aan je abonnement gaan in wanneer {pro} de volgende keer wordt verlengd." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Для тебе діє підписка {app_pro}.

{date} твою підписку буде самодійно поновлено як {current_plan}. Оновлення підписки настане під час наступного оновлення {pro}." + } } } }, "proPlanActivatedAutoShort" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınız aktivdir!

Planınız avtomatik olaraq {date} tarixində başqa bir {current_plan} üçün yenilənəcək." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} je aktivní!

Tarif se automaticky obnoví na další {current_plan} dne {date}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan is active!

Your plan will automatically renew for another {current_plan} on {date}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement is actief!

Je abonnement wordt automatisch verlengd met een {current_plan} op {date}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Для тебе діє підписка {app_pro}.

{date} твою підписку буде самодійно поновлено як {current_plan}." + } } } }, "proPlanActivatedNotAuto" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınızın müddəti {date} tarixində bitir.

Eksklüziv Pro özəlliklərinə kəsintisiz erişimi təmin etmək üçün planınızı indi güncəlləyin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} vyprší dne {date}.

Aktualizujte si tarif nyní, abyste měli i nadále nepřerušený přístup k exkluzivním funkcím Pro." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan will expire on {date}.

Update your plan now to ensure uninterrupted access to exclusive Pro features." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement verloopt op {date}.

Werk je abonnement nu bij om ononderbroken toegang te behouden tot exclusieve Pro functies." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Твоя підписка {app_pro} спливе {date}.

Для збереження особливих можливостей подовж свою підписку." + } + } + } + }, + "proPlanError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} Plan Error" + } } } }, "proPlanExpireDate" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınızın müddəti {date} tarixində bitir." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} vyprší dne {date}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan will expire on {date}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement verloopt op {date}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Підписка {app_pro} спливе {date}." + } + } + } + }, + "proPlanLoading" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} Plan Loading" + } + } + } + }, + "proPlanLoadingDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Information about your {pro} plan is still being loaded. You cannot update your plan until this process is complete." + } + } + } + }, + "proPlanLoadingEllipsis" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} plan loading..." + } + } + } + }, + "proPlanNetworkLoadError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to connect to the network to load your current plan. Updating your plan via {app_name} will be disabled until connectivity is restored.

Please check your network connection and retry." + } } } }, "proPlanNotFound" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planı tapılmadı" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} nebyl nalezen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} Plan Not Found" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} abonnement niet gevonden" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Передплата {pro} не знайдена" + } } } }, "proPlanNotFoundDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hesabınız üçün heç bir aktiv plan tapılmadı. Bunun bir səhv olduğunu düşünürsünüzsə, lütfən kömək üçün {app_name} dəstəyi ilə əlaqə saxlayın." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pro váš účet nebyl nalezen žádný aktivní tarif. Pokud si myslíte, že se jedná o chybu, kontaktujte podporu {app_name} a požádejte o pomoc." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "No active plan was found for your account. If you believe this is a mistake, please reach out to {app_name} support for assistance." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er is geen actief abonnement gevonden voor je account. Als je denkt dat dit een vergissing is, neem dan contact op met de ondersteuning van {app_name} voor hulp." + } } } }, "proPlanPlatformRefund" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başda {platform_store} Mağazası üzərindən {app_pro} üçün qeydiyyatdan keçdiyinizə görə, geri ödəmə tələbini göndərmək üçün eyni {platform_account} hesabını istifadə etməlisiniz." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protože jste se původně zaregistrovali do {app_pro} přes obchod {platform_store}, budete muset pro žádost o vrácení peněz použít stejný účet {platform_account}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Because you originally signed up for {app_pro} via the {platform_store} Store, you'll need to use the same {platform_account} to request a refund." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omdat je je oorspronkelijk hebt aangemeld voor {app_pro} via de {platform_store} winkel, moet je hetzelfde {platform_account} gebruiken om een terugbetaling aan te vragen." + } } } }, "proPlanPlatformRefundLong" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başda {platform_store} Mağazası üzərindən {app_pro} üçün qeydiyyatdan keçdiyinizə görə, geri qaytarma tələbiniz {app_name} Dəstək komandası tərəfindən icra olunacaq.

Aşağıdakı düyməyə basaraq və geri ödəniş formunu dolduraraq geri ödəmə tələbinizi göndərin.

{app_name} Dəstək komandası, geri ödəmə tələblərini adətən 24-72 saat ərzində emal edir, yüksək tələb həcminə görə bu proses daha uzun çəkə bilər." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protože jste si původně zakoupili {app_pro} přes obchod {platform_store}, váš požadavek na vrácení peněz bude zpracován podporou {app_name}.

Požádejte o vrácení peněz kliknutím na tlačítko níže a vyplněním formuláře žádosti o vrácení peněz.

Ačkoliv se podpora {app_name} snaží zpracovat žádosti o vrácení peněz během 24–72 hodin, může zpracování trvat i déle, pokud dochází k vysokému počtu žádostí." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Because you originally signed up for {app_pro} via the {platform_store} Store, your refund request will be processed by {app_name} Support.

Request a refund by hitting the button below and completing the refund request form.

While {app_name} Support strives to process refund requests within 24-72 hours, processing may take longer during times of high request volume." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omdat je je oorspronkelijk hebt aangemeld voor {app_pro} via de {platform_store} winkel, wordt je restitutieverzoek afgehandeld door {app_name} Support.

Vraag een restitutie aan door op de knop hieronder te drukken en het restitutieformulier in te vullen.

Hoewel {app_name} Support ernaar streeft om restitutieverzoeken binnen 24-72 uur te verwerken, kan het tijdens drukte langer duren" + } } } }, "proPlanRecover" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planını geri qaytar" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Znovu nabýt tarif {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Recover {pro} Plan" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} abonnement herstellen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відновити передплату {pro}" + } } } }, "proPlanRenew" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planını yenilə" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obnovit tarif {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Renew {pro} Plan" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} abonnement verlengen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновити підписку {pro}" + } } } }, "proPlanRenewDesktop" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hazırda, {pro} planları, yalnızca {platform_store} və {platform_store} Mağazaları vasitəsilə satın alına və yenilənə bilər. {app_name} Masaüstü istifadə etdiyinizə görə planınızı burada yeniləyə bilməzsiniz.

{app_pro} gəlişdiriciləri, istifadəçilərin {pro} planlarını {platform_store} və {platform_store} Mağazalarından kənarda almağına imkan verəcək alternativ ödəniş variantları üzərində ciddi şəkildə çalışırlar. {pro} Yol Xəritəsi" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "V současnosti lze tarify {pro} zakoupit a obnovit pouze prostřednictvím obchodů {platform_store} nebo {platform_store}. Protože používáte {app_name} Desktop, nemůžete zde svůj plán obnovit.

Vývojáři {app_pro} intenzivně pracují na alternativních platebních možnostech, které by uživatelům umožnily zakoupit tarify {pro} mimo obchody {platform_store} a {platform_store}. Plán vývoje {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Currently, {pro} plans can only be purchased and renewed via the {platform_store} or {platform_store} Stores. Because you are using {app_name} Desktop, you're not able to renew your plan here.

{app_pro} developers are working hard on alternative payment options to allow users to purchase {pro} plans outside of the {platform_store} and {platform_store} Stores. {pro} Roadmap" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Momenteel kunnen {pro} abonnementen alleen worden gekocht en verlengd via de {platform_store}- of {platform_store} winkels. Omdat je {app_name} Desktop gebruikt, kun je je abonnement hier niet verlengen.

De ontwikkelaars van {app_pro} werken hard aan alternatieve betaalmogelijkheden, zodat gebruikers {pro} abonnementen buiten de {platform_store}- en {platform_store} winkels kunnen aanschaffen. {pro} Routekaart" + } } } }, "proPlanRenewDesktopLinked" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_store} və ya {platform_store} Mağazaları vasitəsilə planınızı {app_name} quraşdırılmış və əlaqələndirilmiş cihazda {app_pro} ayarlarında yeniləyin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obnovte svůj tarif v nastavení {app_pro} na propojeném zařízení s nainstalovanou aplikací {app_name} prostřednictvím obchodu {platform_store} nebo {platform_store}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Renew your plan in the {app_pro} settings on a linked device with {app_name} installed via the {platform_store} or {platform_store} Store." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verleng je abonnement in de {app_pro} instellingen op een gekoppeld apparaat met {app_name} geïnstalleerd via de {platform_store} of {platform_store} winkel." + } } } }, "proPlanRenewDesktopStore" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} üçün qeydiyyatdan keçdiyiniz {platform_account} hesabınızla {platform_store} veb saytında planınızı yeniləyin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obnovte svůj tarif na webu {platform_store} pomocí účtu {platform_account}, se kterým jste si pořídili {pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Renew your plan on the {platform_store} website using the {platform_account} you signed up for {pro} with." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verleng je abonnement op de {platform_store} website met het {platform_account} waarmee je je voor {pro} hebt aangemeld." + } } } }, @@ -357655,7 +362680,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Renew your {app_pro} plan to start using powerful {app_pro} features again." + "value" : "Renew your {app_pro} plan to start using powerful {app_pro} Beta features again." } } } @@ -357663,165 +362688,477 @@ "proPlanRenewSupport" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planınız yeniləndi! {network_name} dəstək verdiyiniz üçün təşəkkürlər." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif {app_pro} byl obnoven! Děkujeme, že podporujete síť {network_name}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {app_pro} plan has been renewed! Thank you for supporting the {network_name}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {app_pro} abonnement is verlengd! Bedankt voor je steun aan de {network_name}." + } } } }, "proPlanRestored" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planı bərpa edildi" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} obnoven" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} Plan Restored" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} abonnement hersteld" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "План {pro} відновлено" + } } } }, "proPlanRestoredDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} üçün yararlı bir plan aşkarlandı və {pro} statusunuz bərpa edildi!" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Byl rozpoznán platný tarif {app_pro} a váš stav {pro} byl obnoven!" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "A valid plan for {app_pro} was detected and your {pro} status has been restored!" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Een geldig abonnement voor {app_pro} is gedetecteerd en je {pro} status is hersteld!" + } } } }, "proPlanSignUp" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başda {platform_store} Mağazası üzərindən {app_pro} üçün qeydiyyatdan keçdiyinizə görə planınızı həmin {platform_account} vasitəsilə güncəlləməlisiniz." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protože jste se původně zaregistrovali do {app_pro} přes {platform_store}, je třeba abyste pro aktualizaci vašeho tarifu použili svůj {platform_account}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Because you originally signed up for {app_pro} via the {platform_store} Store, you'll need to use your {platform_account} to update your plan." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omdat je je oorspronkelijk hebt aangemeld voor {app_pro} via de {platform_store} winkel, moet je je {platform_account} gebruiken om je abonnement bij te werken." + } } } }, "proPriceOneMonth" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 ay - {monthly_price}/ay" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 měsíc – {monthly_price} / měsíc" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "1 Month - {monthly_price} / Month" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 maand - {monthly_price} / maand" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 місяць — {monthly_price} / місяць" + } } } }, "proPriceThreeMonths" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 ay - {monthly_price}/ay" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 měsíce – {monthly_price} / měsíc" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "3 Months - {monthly_price} / Month" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 maanden - {monthly_price} / maand" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "3 місяці — {monthly_price} / місяць" + } } } }, "proPriceTwelveMonths" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "12 ay - {monthly_price}/ay" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "12 měsíců – {monthly_price} / měsíc" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "12 Months - {monthly_price} / Month" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "12 maanden - {monthly_price} / maand" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "12 місяців – {monthly_price} / місяць" + } } } }, "proRefundDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Getməyinizə məyus olduq. Geri ödəmə tələb etməzdən əvvəl bilməli olduğunuz şeylər." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mrzí nás, že to rušíte. Než požádáte o vrácení peněz, přečtěte si informace, které byste měli vědět." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "We’re sorry to see you go. Here's what you need to know before requesting a refund." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Het spijt ons dat je vertrekt. Dit moet je weten voordat je een terugbetaling aanvraagt." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шкода, же ти передумав(ла). Перед вимогою повернення грошей ти мусиш знати ось що." + } } } }, "proRefunding" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} geri ödəməsi" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vracení peněz za {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Refunding {pro}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terugbetalen {pro}" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повернення грошей за {pro}" + } } } }, "proRefundingDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} planları üçün geri ödəmələr yalnız {platform_store} Mağazası vasitəsilə {platform_account} tərəfindən həyata keçirilir.

{platform_account} geri ödəniş siyasətlərinə əsasən, {app_name} gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz. Bu, tələbin qəbul olunub-olunmaması ilə yanaşı, tam və ya qismən geri ödənişin verilib-verilməməsini də əhatə edir." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vrácení peněz za tarify {app_pro} je vyřizováno výhradně prostřednictvím {platform_account} v obchodě {platform_store}.

Vzhledem k pravidlům vracení peněz služby {platform_account} nemají vývojáři {app_name} žádný vliv na výsledek žádostí o vrácení peněz. To zahrnuje i rozhodnutí, zda bude žádost schválena nebo zamítnuta, a také, zda bude vrácena část peněz, nebo všechny peníze." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Refunds for {app_pro} plans are handled exclusively by {platform_account} through the {platform_store} Store.

Due to {platform_account} refund policies, {app_name} developers have no ability to influence the outcome of refund requests. This includes whether the request is approved or denied, as well as whether a full or partial refund is issued." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terugbetalingen voor {app_pro} abonnementen worden uitsluitend afgehandeld door {platform_account} via de {platform_store} Store.

Vanwege het restitutiebeleid van {platform_account} hebben ontwikkelaars van {app_name} geen invloed op de uitkomst van terugbetalingsverzoeken. Dit geldt zowel voor de goedkeuring of afwijzing van het verzoek als voor het wel of niet toekennen van een volledige of gedeeltelijke terugbetaling." + } } } }, "proRefundNextSteps" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} hazırda geri ödəniş tələbinizi emal edir. Bu, adətən 24-48 saat çəkir. Onların qərarından asılı olaraq, {app_name} tətbiqində {pro} statusunuzun dəyişdiyini görə bilərsiniz." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} nyní zpracovává vaši žádost o vrácení peněz. Obvykle to trvá 24–48 hodin. V závislosti na jejich rozhodnutí se může váš stav {pro} v aplikaci {app_name} změnit." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{platform_account} is now processing your refund request. This typically takes 24-48 hours. Depending on their decision, you may see your {pro} status change in {app_name}." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_account} verwerkt nu je terugbetalingsverzoek. Dit duurt meestal 24-48 uur. Afhankelijk van hun beslissing kan je {pro} status wijzigen in {app_name}." + } } } }, "proRefundRequestSessionSupport" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri qaytarma tələbiniz {app_name} Dəstək komandası tərəfindən icra olunacaq.

Aşağıdakı düyməyə basaraq və geri ödəniş formunu dolduraraq geri ödəniş tələbinizi göndərin.

{app_name} Dəstək komandası, geri ödəniş tələblərini adətən 24-72 saat ərzində emal edir, yüksək tələb həcminə görə bu proses daha uzun çəkə bilər." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš požadavek na vrácení peněz bude zpracován podporou {app_name}.

Požádejte o vrácení peněz kliknutím na tlačítko níže a vyplněním formuláře žádosti o vrácení peněz.

Ačkoliv se podpora {app_name} snaží zpracovat žádosti o vrácení peněz během 24–72 hodin, může zpracování trvat i déle, v případě že je vyřizováno mnoho žádostí." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your refund request will be handled by {app_name} Support.

Request a refund by hitting the button below and completing the refund request form.

While {app_name} Support strives to process refund requests within 24-72 hours, processing may take longer during times of high request volume." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je restitutieverzoek wordt afgehandeld door {app_name} Support.

Vraag een restitutie aan door op de knop hieronder te drukken en het restitutieformulier in te vullen.

Hoewel {app_name} Support ernaar streeft om restitutieverzoeken binnen 24-72 uur te verwerken, kan het tijdens drukte langer duren." + } } } }, "proRefundRequestStorePolicies" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri ödəniş tələbiniz yalnız {platform_account} veb saytında {platform_account} hesabı üzərindən icra olunacaq.

{platform_account} geri ödəniş siyasətlərinə əsasən, {app_name} gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz. Bu, tələbin qəbul olunub-olunmaması ilə yanaşı, tam və ya qismən geri ödənişin verilib-verilməməsini də əhatə edir." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaši žádost o vrácení peněz bude vyřizovat výhradně {platform_account} prostřednictvím webových stránek {platform_account}.

Vzhledem k pravidlům vracení peněz {platform_account} nemají vývojáři {app_name} žádný vliv na výsledek žádostí o vrácení peněz. To zahrnuje i rozhodnutí, zda bude žádost schválena nebo zamítnuta, a také, zda bude vrácena část peněz, nebo všechny peníze." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your refund request will be handled exclusively by {platform_account} through the {platform_account} website.

Due to {platform_account} refund policies, {app_name} developers have no ability to influence the outcome of refund requests. This includes whether the request is approved or denied, as well as whether a full or partial refund is issued." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je terugbetalingsverzoek wordt uitsluitend afgehandeld door {platform_account} via de website van {platform_account}.

Vanwege het restitutiebeleid van {platform_account} hebben ontwikkelaars van {app_name} geen invloed op de uitkomst van terugbetalingsverzoeken. Dit geldt zowel voor de goedkeuring of afwijzing van het verzoek als voor het wel of niet toekennen van een volledige of gedeeltelijke terugbetaling." + } } } }, "proRefundSupport" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri ödəmə tələbinizlə bağlı daha çox güncəlləmə üçün lütfən {platform_account} ilə əlaqə saxlayın. {platform_account} geri ödəniş siyasətlərinə əsasən, {app_name} gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz.

{platform_store} Geri ödəmə dəstəyi" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pro další informace o vaší žádosti o vrácení peněz kontaktujte prosím {platform_account}. Vzhledem k zásadám pro vrácení peněz {platform_account} nemají vývojáři aplikace {app_name} žádnou možnost ovlivnit výsledek žádosti o vrácení.

Podpora vrácení peněz {platform_store}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Please contact {platform_account} for further updates on your refund request. Due to {platform_account} refund policies, {app_name} developers have no ability to influence the outcome of refund requests.

{platform_store} Refund Support" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neem contact op met {platform_account} voor verdere updates over je restitutieverzoek. Vanwege het restitutiebeleid van {platform_account} hebben de ontwikkelaars van {app_name} geen invloed op de uitkomst van restitutieverzoeken.

{platform_store} Terugbetalingsondersteuning" + } } } }, "proRequestedRefund" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri ödəmə tələb edildi" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Žádost o vrácení peněz" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Refund Requested" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terugbetaling aangevraagd" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вимогу повернення грошей надіслано" + } } } }, @@ -357965,99 +363302,402 @@ "proSettings" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} ayarları" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastavení {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} Settings" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} instellingen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштування {pro}" + } } } }, "proStats" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} statistikalarınız" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaše statistiky {pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your {pro} Stats" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je {pro} statistieken" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваша статистика {pro}" + } + } + } + }, + "proStatsLoading" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} Stats Loading" + } + } + } + }, + "proStatsLoadingDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your {pro} stats are loading, please wait." + } } } }, "proStatsTooltip" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} statistikaları, bu cihazdakı istifadəni əks-etdirir və əlaqələndirilmiş cihazlarda fərqli görünə bilər." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Statistiky {pro} ukazují používání na tomto zařízení a mohou se lišit na jiných propojených zařízeních" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{pro} stats reflect usage on this device and may appear differently on linked devices" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} statistieken weerspiegelen het gebruik op dit apparaat en kunnen anders weergegeven worden op gekoppelde apparaten" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Звіти підписки {pro} відображають використання лише цього пристрою, тож, мабуть, матимуть иншого вигляду на инших пристроях" + } + } + } + }, + "proStatusError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} Status Error" + } + } + } + }, + "proStatusInfoInaccurateNetworkError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to connect to the network to check your {pro} status. Information displayed on this page may be inaccurate until connectivity is restored.

Please check your network connection and retry." + } + } + } + }, + "proStatusLoading" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} Status Loading" + } + } + } + }, + "proStatusLoadingDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your {pro} information is being loaded. Some actions on this page may be unavailable until loading is complete." + } + } + } + }, + "proStatusLoadingSubtitle" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} status loading" + } + } + } + }, + "proStatusNetworkErrorDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to connect to the network to check your {pro} status. You cannot upgrade to {pro} until connectivity is restored.

Please check your network connection and retry." + } + } + } + }, + "proStatusRefreshNetworkError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to connect to the network to refresh your {pro} status. Some actions on this page will be disabled until connectivity is restored.

Please check your network connection and retry." + } } } }, "proSupportDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{pro} planınızla bağlı kömək lazımdır? Dəstək komandamıza müraciət edin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Potřebujete pomoc se svým tarifem {pro}? Pošlete žádost týmu podpory." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Need help with your {pro} plan? Submit a request to the support team." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hulp nodig met je {pro} abonnement? Dien een verzoek in bij het ondersteuningsteam." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Якщо потребуєш допомоги щодо підписки {pro}, надійшли звернення до відділу підтримки." + } } } }, "proTosPrivacy" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncəlləyərək, {app_pro} Xidmət Şərtləri {icon} və Məxfilik Siyasəti {icon} ilə razılaşırsınız" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualizací souhlasíte s Podmínkami služby {icon} a Zásadami ochrany osobních údajů {icon} {app_pro}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "By updating, you agree to the {app_pro} Terms of Service {icon} and Privacy Policy {icon}" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Door bij te werken ga je akkoord met de {app_pro} Gebruiksvoorwaarden {icon} en het Privacybeleid {icon}" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Цією дією ти надаси згоду щодо дотримання Правил послуги {app_pro} {icon} і Ставлення до особистих відомостей {icon}" + } } } }, "proUnlimitedPins" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limitsiz sancma" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neomezený počet připnutí" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Unlimited Pins" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Onbeperkte Pins" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Необмежена кількість закріплених бесід" + } } } }, "proUnlimitedPinsDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limitsiz sancılmış danışıqla bütün söhbətlərinizi təşkil edin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Organizujte si komunikaci pomocí neomezeného počtu připnutých konverzací." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Organize all your chats with unlimited pinned conversations." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Organiseer al je chats met onbeperkt vastgezette gesprekken." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Закріплення необмеженої кількості співрозмовників в головному переліку." + } } } }, "proUpdatePlanDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hazırda {current_plan} Planı üzərindəsiniz. {selected_plan} Planınana keçmək istədiyinizə əminsiniz?

Güncəlləsəniz, planınız {date} tarixində əlavə {selected_plan} {pro} erişimi üçün avtomatik yenilənəcək." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "V současné době jste na tarifu {current_plan}. Jste si jisti, že chcete přepnout na tarif {selected_plan}?

Po aktualizaci bude váš tarif automaticky obnoven {date} na další {selected_plan} přístupu {pro}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "You are currently on the {current_plan} Plan. Are you sure you want to switch to the {selected_plan} Plan?

By updating, your plan will automatically renew on {date} for an additional {selected_plan} of {pro} access." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je zit momenteel op het {current_plan} abonnement. Weet je zeker dat je wilt overschakelen naar het {selected_plan} abonnement?

Als je dit bijwerkt, wordt je abonnement op {date} automatisch verlengd met {selected_plan} {pro} toegang." + } } } }, "proUpdatePlanExpireDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planınız {date} tarixində bitəcək.

Güncəlləsəniz, planınız {date} tarixində əlavə {selected_plan} Pro erişimi üçün avtomatik olaraq yenilənəcək." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Váš tarif vyprší {date}.

Po aktualizaci se váš tarif automaticky obnoví {date} na další {selected_plan} přístupu Pro." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your plan will expire on {date}.

By updating, your plan will automatically renew on {date} for an additional {selected_plan} of Pro access." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je abonnement verloopt op {date}.

Door bij te werken wordt je abonnement automatisch verlengd op {date} voor een extra {selected_plan} aan Pro-toegang." + } } } }, @@ -358154,12 +363794,24 @@ "value" : "Vrei să profiți mai mult de {app_name}? Fă upgrade la {app_pro} pentru o experiență de mesagerie mai puternică." } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хотите больше возможностей от {app_name}? Перейдите на {app_pro}, чтобы получить более мощный опыт обмена сообщениями." + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Vill du få ut mer av {app_name}? Uppgradera till {app_pro} för en kraftfullare meddelandeupplevelse." } }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name}'den daha fazla yararlanmak ister misiniz? Daha güçlü bir mesajlaşma deneyimi için {app_pro}'ya yükseltin." + } + }, "uk" : { "stringUnit" : { "state" : "translated", @@ -364408,6 +370060,18 @@ "value" : "응답 수신됨" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "وەڵامی وەرگیراو" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "وەڵامی وەرگیراو" + } + }, "nb" : { "stringUnit" : { "state" : "translated", @@ -364593,6 +370257,18 @@ "value" : "통화 제안 받는 중" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "پێشنیاری پەیوەندی بنێرە" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "پێشنیاری پەیوەندی بنێرە" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -366256,11 +371932,59 @@ "recoveryPasswordDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hesabınızı yeni cihazlara yükləmək üçün geri qaytarma parolunuzu istifadə edin.

Geri qaytarma parolunuz olmadan hesabınız geri qaytarıla bilməz. Parolu təhlükəsiz və etibarlı yerdə saxladığınıza əmin olun və heç kəslə paylaşmayın." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Použijte své heslo pro obnovení pro načtení účtu na nových zařízeních.

Bez hesla pro obnovení nelze obnovit účet. Ujistěte se, že je uložené na bezpečném místě — a nesdílejte ho s nikým." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwende dein Wiederherstellungspasswort, um deinen Account auf neue Geräten zu laden.

Dein Account kann ohne dein Wiederherstellungspasswort nicht wiederhergestellt werden. Stelle sicher, dass es an einem sicheren Ort aufbewahrt ist – und teile es niemandem mit." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Use your recovery password to load your account on new devices.

Your account cannot be recovered without your recovery password. Make sure it's stored somewhere safe and secure — and don't share it with anyone." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gebruik uw herstelwachtwoord om uw account op nieuwe apparaten te laden.

Uw account kan niet worden hersteld zonder uw herstelwachtwoord. Zorg ervoor dat het ergens veilig is opgeslagen – en deel het met niemand." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Используйте пароль восстановления, чтобы загрузить свою учетную запись на новых устройствах.

Ваша учетная запись не может быть восстановлена без пароля восстановления. Убедитесь, что он хранится в безопасном месте — и не делитесь им ни с кем." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Använd ditt återställningslösenord för att ladda ditt konto på nya enheter.

Ditt konto kan inte återställas utan ditt återställningslösenord. Se till att det lagras på en säker och trygg plats — och dela det inte med någon." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kurtarma şifrenizi kullanarak hesabınızı yeni cihazlara yükleyin.

Kurtarma şifreniz olmadan hesabınız kurtarılamaz. Şifrenizi güvenli bir yerde sakladığınızdan emin olun ve kimseyle paylaşmayın." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Використовуйте пароль для відновлення для завантаження свого облікового запису на нових пристроях.

Ваш обліковий запис не може бути відновлений без пароля для відновлення. Переконайтеся, що він зберігається у надійному місці та не передавайте його нікому." + } } } }, @@ -370272,11 +375996,71 @@ "recoveryPasswordHidePermanentlyDescription2" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri qaytarma parolunuzu bu cihazdan həmişəlik gizlətmək istədiyinizə əminsiniz?

Bunun geri dönüşü yoxdur." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opravdu chcete trvale skrýt heslo pro obnovení na tomto zařízení?

Tuto akci nelze vrátit." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bist du sicher, dass du dein Wiederherstellungspasswort auf diesem Gerät dauerhaft ausblenden möchtest?

Dies kann nicht mehr rückgängig gemacht werden." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Are you sure you want to permanently hide your recovery password on this device?

This cannot be undone." } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Êtes-vous sûr de vouloir cacher définitivement votre mot de passe de récupération sur cet appareil ?

Cette action est irréversible." + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er du sikker på at du har lyst til å permanent skjule gjenopprettingspassordet ditt?

Dette kan ikke angres." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Weet u zeker dat u uw herstelwachtwoord permanent wilt verbergen op dit apparaat?

Dit kan niet ongedaan gemaakt worden." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вы уверены, что хотите навсегда скрыть ваш пароль восстановления на этом устройстве?

Это действие не может быть отменено." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Är du säker på att du vill dölja ditt återställningslösenord permanent på denna enhet?

Detta kan inte ångras." + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bu cihazda kurtarma şifrenizi kalıcı olarak gizlemek istediğinizden emin misiniz?

Bu işlem geri alınamaz." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ви впевнені, що хочете назавжди приховати пароль для відновлення на цьому пристрої?

Цю дію неможливо скасувати." + } } } }, @@ -371720,22 +377504,118 @@ "recoveryPasswordView" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri qaytarma paroluna bax" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zobrazit heslo pro obnovení" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wiederherstellungspasswort anzeigen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "View Recovery Password" } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vis Gjenopprettingspassord" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bekijk Herstelwachtwoord" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Показать пароль восстановления" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visa återställningslösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Перегляд паролю для відновлення" + } } } }, "recoveryPasswordVisibility" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri qaytarma parolu görünməsi" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Viditelnost hesla pro obnovení" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sichtbarkeit des Wiederherstellungspassworts" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Recovery Password Visibility" } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gjenopprettingspassord Synlighet" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zichtbaarheid herstelwachtwoord" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Видимость пароля восстановления" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Synlighet för återställningslösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Видимість пароля для відновлення" + } } } }, @@ -372879,11 +378759,29 @@ "refundPlanNonOriginatorApple" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Başda fərqli {platform_account} vasitəsilə {app_pro} üçün qeydiyyatdan keçdiyinizə görə planınızı həmin {platform_account} vasitəsilə güncəlləməlisiniz." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protože jste se původně zaregistrovali do {app_pro} přes jiný {platform_account}, je třeba použít ten {platform_account}, abyste aktualizovali váš tarif." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Because you originally signed up for {app_pro} via a different {platform_account}, you'll need to use that {platform_account} to update your plan." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Omdat je je oorspronkelijk hebt aangemeld voor {app_pro} via een ander {platform_account}, moet je datzelfde {platform_account} gebruiken om je abonnement bij te werken." + } } } }, @@ -373255,6 +379153,24 @@ "value" : "{count}자 입력 가능" } }, + "nb" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld tegn igjen" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld tegn igjen" + } + } + } + } + }, "nl" : { "variations" : { "plural" : { @@ -374428,22 +380344,88 @@ "removePasswordModalDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} üçün hazırkı parolunuzu silin. Daxili olaraq saxlanılmış verilər, cihazınızda saxlanılan təsadüfi yaradılmış açarla təkrar şifrələnəcək." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odstraňte své aktuální heslo pro {app_name}. Lokálně uložená data budou znovu zašifrována pomocí náhodně vygenerovaného klíče uloženého ve vašem zařízení." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entferne dein aktuelles Passwort für {app_name}. Lokal gespeicherte Daten werden mit einem zufällig generierten Schlüssel, der auf deinem Gerät gespeichert wird, erneut verschlüsselt." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Remove your current password for {app_name}. Locally stored data will be re-encrypted with a randomly generated key, stored on your device." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwijder je huidige wachtwoord voor {app_name}. Lokaal opgeslagen gegevens worden opnieuw versleuteld met een willekeurig gegenereerde sleutel, opgeslagen op je apparaat." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Удалите текущий пароль для {app_name}. Локально сохранённые данные будут повторно зашифрованы с использованием случайно сгенерированного ключа, хранящегося на вашем устройстве." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ta bort ditt nuvarande lösenord för {app_name}. Lokalt lagrad data kommer att krypteras om med en slumpmässigt genererad nyckel som lagras på din enhet." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Видаліть свій поточний пароль для {app_name}. Локально збережені дані буде повторно зашифровано випадково згенерованим ключем, який зберігатиметься на вашому пристрої." + } } } }, "renew" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yenilə" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obnovit" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Renew" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verlengen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поновити" + } } } }, @@ -374929,11 +380911,35 @@ "requestRefund" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri ödəmə tələb et" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Požádat o vrácení platby" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Request Refund" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terugbetaling aanvragen" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Запит на повернення коштів" + } } } }, @@ -381414,6 +387420,28 @@ } } }, + "screenshotProtectionDescriptionDesktop" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Conceal the {app_name} window in screenshots taken on this device." + } + } + } + }, + "screenshotProtectionDesktop" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screenshot Protection" + } + } + } + }, "screenshotTaken" : { "extractionState" : "manual", "localizations" : { @@ -389621,6 +395649,18 @@ "value" : "연결 후보 전송 중" } }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی کاندیدەکانی پەیوەندی" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ناردنی کاندیدەکانی پەیوەندی" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -395972,11 +402012,35 @@ "sessionProBeta" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} Beta" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} Beta" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "{app_pro} Beta" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} Bèta" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_pro} бета" + } } } }, @@ -397581,22 +403645,76 @@ "setPasswordModalDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{app_name} üçün bir parol təyin edin. Daxili olaraq saxlanılmış verilər, bu parolla şifrələnəcək. {app_name} tətbiqini hər başlatdıqda, sizdən bu parolu daxil etməyiniz istənəcək." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nastavte heslo pro {app_name}. Lokálně uložená data budou šifrována tímto heslem. Při každém spuštění {app_name} budete vyzváni k zadání tohoto hesla." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Set a password for {app_name}. Locally stored data will be encrypted with this password. You will be asked to enter this password each time {app_name} starts." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stel een wachtwoord in voor {app_name}. Lokaal opgeslagen gegevens worden versleuteld met dit wachtwoord. Je wordt gevraagd dit wachtwoord in te voeren telkens wanneer {app_name} wordt gestart." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Установите пароль для {app_name}. Локально сохранённые данные будут зашифрованы с использованием этого пароля. При каждом запуске {app_name} вам потребуется вводить этот пароль." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ställ in ett lösenord för {app_name}. Lokalt lagrade data kommer att krypteras med detta lösenord. Du kommer att bli ombedd att ange detta lösenord varje gång {app_name} startas." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Встановіть пароль для {app_name}. Дані, збережені локально, буде зашифровано цим паролем. Цей пароль запитуватиметься при кожному запуску {app_name}." + } } } }, "settingsCannotChangeDesktop" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ayar güncəllənə bilmir" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nelze aktualizovat volbu" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Cannot Update Setting" } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Неможливо оновити налаштування" + } } } }, @@ -398079,14 +404197,511 @@ } } }, + "settingsScreenSecurityDesktop" : { + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skermveiligheid" + } + }, + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "أمان الشاشة" + } + }, + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekran güvənliyi" + } + }, + "bal" : { + "stringUnit" : { + "state" : "translated", + "value" : "سکرین سیکورٹی" + } + }, + "be" : { + "stringUnit" : { + "state" : "translated", + "value" : "Бяспека экрану" + } + }, + "bg" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сигурност на екрана" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "স্ক্রীন সিকিউরিটি" + } + }, + "ca" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seguretat de pantalla" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zabezpečení obrazovky" + } + }, + "cy" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diogelu'r sgrin" + } + }, + "da" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skærmsikkerhed" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bildschirmschutz" + } + }, + "el" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ασφάλεια Οθόνης" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screen Security" + } + }, + "eo" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekrana sekurigo" + } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seguridad de pantalla" + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seguridad de pantalla" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekraani turvalisus" + } + }, + "eu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pantailaren Segurtasuna" + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "امنیت صفحه نمایش" + } + }, + "fi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Näytön suojaus" + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screen Security" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sécurité d'écran" + } + }, + "gl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seguranza da pantalla" + } + }, + "ha" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tsaron Allo" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אבטחת מסך" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "स्क्रीन सुरक्षा" + } + }, + "hr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sigurnost zaslona" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Képernyőbiztonság" + } + }, + "hy-AM" : { + "stringUnit" : { + "state" : "translated", + "value" : "Էկրանի անվտանգություն" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keamanan Layar" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sicurezza Schermo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スクリーンセキュリティ" + } + }, + "ka" : { + "stringUnit" : { + "state" : "translated", + "value" : "ეკრანის დაცვა" + } + }, + "km" : { + "stringUnit" : { + "state" : "translated", + "value" : "សុវត្ថិភាពអេក្រង់" + } + }, + "kn" : { + "stringUnit" : { + "state" : "translated", + "value" : "ಪರದೆಯ ಭದ್ರತೆ" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "화면 보안" + } + }, + "ku" : { + "stringUnit" : { + "state" : "translated", + "value" : "پاراستنی پردە" + } + }, + "ku-TR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parastina Ekranê" + } + }, + "lg" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obukuumi bwa ekikola ekiriko akabonero" + } + }, + "lt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekrano saugumas" + } + }, + "lv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekrāna drošība" + } + }, + "mk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Екранска Безбедност" + } + }, + "mn" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дэлгэцийн аюулгүй байдал" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keselamatan Skrin" + } + }, + "my" : { + "stringUnit" : { + "state" : "translated", + "value" : "မျက်နှာပြင် လုံခြုံရေး" + } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skjermsikkerhet" + } + }, + "nb-NO" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skjermsikkerhet" + } + }, + "ne-NP" : { + "stringUnit" : { + "state" : "translated", + "value" : "स्क्रीन सुरक्षा" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scherm beveiliging" + } + }, + "nn-NO" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skjermtryggleik" + } + }, + "ny" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rikuripa pakallayachina" + } + }, + "pa-IN" : { + "stringUnit" : { + "state" : "translated", + "value" : "ਸਕ੍ਰੀਨ ਸੁਰੱਖਿਆ" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ochrona ekranu" + } + }, + "ps" : { + "stringUnit" : { + "state" : "translated", + "value" : "د سکرین امنیت" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Segurança de Tela" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Segurança de ecrã" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Securitate ecran" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Защита экрана" + } + }, + "sh" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sigurnost ekrana" + } + }, + "si-LK" : { + "stringUnit" : { + "state" : "translated", + "value" : "තිර ආරක්ෂාව" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zabezpečenie obrazovky" + } + }, + "sl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Varnost zaslona" + } + }, + "sq" : { + "stringUnit" : { + "state" : "translated", + "value" : "Siguri ekrani" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Безбедност екрана" + } + }, + "sr-Latn" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bezbednost ekrana" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skärmsäkerhet" + } + }, + "sw" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usalama wa Skrini" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "திரை பாதுகாப்பு" + } + }, + "te" : { + "stringUnit" : { + "state" : "translated", + "value" : "స్క్రీన్ భద్రత" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ความปลอดภัยหน้าจอ" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekran Güvenliği" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Безпека перегляду" + } + }, + "ur-IN" : { + "stringUnit" : { + "state" : "translated", + "value" : "سکرین سیکیورٹی" + } + }, + "uz" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekran xavfsizligi" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "An ninh màn hình" + } + }, + "xh" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ukhuseleko lweSikrini" + } + }, + "zh-CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "屏幕安全性" + } + }, + "zh-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "螢幕安全性" + } + } + } + }, "settingsStartCategoryDesktop" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Açılış" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spuštění" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Startup" } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Автозапуск" + } } } }, @@ -402390,11 +409005,53 @@ "spellChecker" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yazı yoxlanışı" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kontrola pravopisu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rechtschreibprüfung" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Spell Checker" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spellingcontrole" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Проверка орфографии" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stavningskontroll" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Перевірка орфографії" + } } } }, @@ -402880,22 +409537,112 @@ "strength" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gücü" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Síla" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwortstärke" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Strength" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sterkte" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Puternic" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Надёжность" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Styrka" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Надійність" + } } } }, "supportDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Problemlə üzləşmisiniz? Kömək məqalələrini oxuyun, ya da {app_name} Dəstək ilə bir sorğu açın." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Máte problémy? Projděte si články nápovědy nebo kontaktujte podporu {app_name}." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Probleme? Erkunde die Hilfeartikel oder öffne ein Ticket bei dem {app_name} Support." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Having issues? Explore help articles or open a ticket with {app_name} Support." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Problemen? Bekijk de hulpartikelen of open een ticket bij {app_name} Support." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Возникли проблемы? Ознакомьтесь со статьями в справке или отправьте запрос в службу поддержки {app_name}." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Har du problem? Utforska hjälpartiklar eller öppna en supportförfrågan hos {app_name}." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Виникли проблеми? Перегляньте довідкові статті або створіть запит до служби підтримки {app_name}." + } } } }, @@ -403905,7 +410652,7 @@ "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Toca para reintentar" + "value" : "Toque para reintentar" } }, "es-ES" : { @@ -405482,22 +412229,88 @@ "themePreview" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tema önizləməsi" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Náhled motivu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Design-Vorschau" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Theme Preview" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thema voorbeeld" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Предпросмотр темы" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Förhandsvisning av tema" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Попередній перегляд теми" + } } } }, "theReturn" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Qayıt" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zpět" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Return" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terug" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Зворотно" + } } } }, @@ -405766,22 +412579,100 @@ "translate" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tərcümə et" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Překlad" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Übersetzen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Translate" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vertalen" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Перевод" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Översätt" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Переклад" + } } } }, "tray" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sini" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lišta" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Tray" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Systeemvak" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Трей" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivitetsfält" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Область сповіщень" + } } } }, @@ -408329,6 +415220,17 @@ } } }, + "unsupportedCpu" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unsupported CPU" + } + } + } + }, "updateApp" : { "extractionState" : "manual", "localizations" : { @@ -408907,12 +415809,24 @@ "value" : "Actualizează informațiile comunității" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обновить информацию о сообществе" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Uppdatera communityinformation" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновити інформацію про спільноту" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -409020,12 +415934,24 @@ "value" : "Numele și descrierea comunității sunt vizibile pentru toți membrii comunității" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Название и описание Community видны всем участникам Community" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Communitynamn och beskrivning är synliga för alla communitymedlemmar" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Назву та опис спільноти бачать усі учасники" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -409133,12 +416059,24 @@ "value" : "Te rugăm să introduci o descriere a comunității mai scurtă" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Введите более короткое описание сообщества" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Vänligen ange en kortare communitybeskrivning" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Будь ласка, введіть коротший опис спільноти" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -409246,12 +416184,24 @@ "value" : "Te rugăm să introduci un nume al comunității mai scurt" } }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пожалуйста, введите более короткое название сообщества" + } + }, "sv-SE" : { "stringUnit" : { "state" : "translated", "value" : "Vänligen ange ett kortare communitynamn" } }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Будь ласка, введіть коротшу назву спільноти" + } + }, "zh-CN" : { "stringUnit" : { "state" : "translated", @@ -412428,6 +419378,12 @@ "value" : "Versîyoneke nû ({version}) ya {app_name} berdest e." } }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "En ny versjon ({version}) av {app_name} er tilgjengelig." + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -412499,44 +419455,200 @@ "updatePlan" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planı güncəllə" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualizovat tarif" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Update Plan" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abonnement bijwerken" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновити тарифний план" + } } } }, "updatePlanTwo" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planınızı güncəlləməyin iki yolu var:" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dva způsoby, jak aktualizovat váš tarif:" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Two ways to update your plan:" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Twee manieren om je abonnement bij te werken:" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Два шляхи поновлення твоєї підписки:" + } } } }, "updateProfileInformation" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil məlumatlarını güncəllə" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Upravit informace profilu" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profilinformationen aktualisieren" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Update Profile Information" } + }, + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizar información de perfil" + } + }, + "es-ES" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizar información de perfil" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profielinformatie bijwerken" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizarea informațiilor de profil" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обновить информацию профиля" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uppdatera profilinformation" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновити інформацію облікового запису" + } } } }, "updateProfileInformationDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ekran adınız və ekran şəkliniz bütün danışıqlarda görünür." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaše zobrazované jméno a profilová fotka jsou viditelné ve všech konverzacích." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dein Anzeigename und Profilbild sind in allen Unterhaltungen sichtbar." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your display name and display picture are visible in all conversations." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je weergavenaam en profielfoto zijn zichtbaar in alle gesprekken." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Numele și poza de profil sunt vizibile în toate conversațiile tale." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваше отображаемое имя и фотография профиля видны во всех беседах." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt visningsnamn och din visningsbild är synliga i alla konversationer." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваше відображуване ім’я та зображення профілю видимі у всіх розмовах." + } } } }, @@ -413022,11 +420134,53 @@ "updates" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncəlləmələr" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualizace" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualisierungen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Updates" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Updates" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обновления" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uppdateringar" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновлення" + } } } }, @@ -413997,11 +421151,53 @@ "updating" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Güncəllənir..." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualizuji..." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wird aktualisiert..." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Updating..." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bijwerken..." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizare..." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uppdaterar..." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оновлення..." + } } } }, @@ -416540,11 +423736,35 @@ "urlOpenDescriptionAlternative" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keçidlər, brauzerinizdə açılacaq." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odkazy se otevřou ve vašem prohlížeči." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Links will open in your browser." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Links worden in uw browser geopend." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "За ланкою перейде твоє оглядало мережців за промовчання." + } } } }, @@ -417030,22 +424250,58 @@ "viaStoreWebsite" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "{platform_store} veb saytı vasitəsilə" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Přes webové stránky {platform_store}" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Via the {platform_store} website" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Via de {platform_store} website" + } } } }, "viaStoreWebsiteDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Qeydiyyatdan keçərkən istifadə etdiyiniz {platform_account} hesabı ilə {platform_store} veb saytı üzərindən planınızı dəyişdirin." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Změňte svůj tarif pomocí {platform_account}, se kterým jste se zaregistrovali, prostřednictvím webu {platform_store}." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Change your plan using the {platform_account} you used to sign up with, via the {platform_store} website." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wijzig je abonnement met het {platform_account} waarmee je je hebt aangemeld, via de {platform_store} website." + } } } }, @@ -421682,36 +428938,179 @@ } } }, + "yourCpuIsUnsupportedSSE42" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your CPU does not support SSE 4.2, which is required for Session to run on Linux x64. Please upgrade your CPU or use a different operating system." + } + } + } + }, "yourRecoveryPassword" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geri qaytarma parolunuz" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaše heslo pro obnovení" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dein Wiederherstellungspasswort" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Your Recovery Password" } + }, + "nb" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt Gjenopprettingspassord" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je herstelwachtwoord" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш Пароль Восстановления" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt återställningslösenord" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш пароль для відновлення" + } } } }, "zoomFactor" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Böyütmə amili" + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Měřítko přiblížení" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vergrößerungsfaktor" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Zoom Factor" } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zoomfactor" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Масштабирование приложения" + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zoomfaktor" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Масштаб" + } } } }, "zoomFactorDescription" : { "extractionState" : "manual", "localizations" : { + "az" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mətnin və vizual elementlərin ölçüsünü ayarla." + } + }, + "cs" : { + "stringUnit" : { + "state" : "translated", + "value" : "Upravte velikost textu a vizuálních prvků." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passe die Größe von Text und visuellen Elementen an." + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Adjust the size of text and visual elements." } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pas de grootte van tekst en visuele elementen aan." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Настройте размер текста и визуальных элементов." + } + }, + "sv-SE" : { + "stringUnit" : { + "state" : "translated", + "value" : "Justera storleken på text och visuella element." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Налаштування розміру тексту та візуальних елементів." + } } } } diff --git a/Session/Notifications/NotificationActionHandler.swift b/Session/Notifications/NotificationActionHandler.swift index 5c0999824b..083aa94356 100644 --- a/Session/Notifications/NotificationActionHandler.swift +++ b/Session/Notifications/NotificationActionHandler.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SignalUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 6a885dfd2c..5d32a61072 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit @@ -82,7 +82,7 @@ public enum SyncPushTokensJob: JobExecutor { // Unregister from our server if let existingToken: String = lastRecordedPushToken { Log.info(.syncPushTokensJob, "Unregister using last recorded push token: \(redact(existingToken))") - return PushNotificationAPI + return Network.PushNotification .unsubscribeAll(token: Data(hex: existingToken), using: dependencies) .map { _ in () } .eraseToAnyPublisher() @@ -177,7 +177,7 @@ public enum SyncPushTokensJob: JobExecutor { } Log.info(.syncPushTokensJob, "Sending push token to PN server") - return PushNotificationAPI + return Network.PushNotification .subscribeAll( token: Data(hex: pushToken), isForcedUpdate: true, diff --git a/Session/Onboarding/LoadingScreen.swift b/Session/Onboarding/LoadingScreen.swift index 4fffd9fe46..b731acafbd 100644 --- a/Session/Onboarding/LoadingScreen.swift +++ b/Session/Onboarding/LoadingScreen.swift @@ -3,7 +3,7 @@ import SwiftUI import Combine import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SignalUtilitiesKit import SessionUtilitiesKit diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index 00eb1600a2..322bf5b1ee 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -5,7 +5,7 @@ import Combine import GRDB import SessionUtilitiesKit import SessionMessagingKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Cache diff --git a/Session/Onboarding/PNModeScreen.swift b/Session/Onboarding/PNModeScreen.swift index d4976596a1..40100bf54e 100644 --- a/Session/Onboarding/PNModeScreen.swift +++ b/Session/Onboarding/PNModeScreen.swift @@ -3,7 +3,7 @@ import SwiftUI import Combine import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SignalUtilitiesKit import SessionUtilitiesKit diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 5b3f4e4177..0c5fa3c2cc 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -6,6 +6,7 @@ import AVFoundation import GRDB import SessionUIKit import SessionMessagingKit +import SessionNetworkingKit import SessionUtilitiesKit import SignalUtilitiesKit @@ -485,11 +486,11 @@ private final class EnterURLVC: UIViewController, UIGestureRecognizerDelegate, O ) } - func join(_ room: OpenGroupAPI.Room) { + func join(_ room: Network.SOGS.Room) { joinOpenGroupVC?.joinOpenGroup( roomToken: room.token, - server: OpenGroupAPI.defaultServer, - publicKey: OpenGroupAPI.defaultServerPublicKey, + server: Network.SOGS.defaultServer, + publicKey: Network.SOGS.defaultServerPublicKey, shouldOpenCommunity: true, onError: nil ) diff --git a/Session/Open Groups/OpenGroupSuggestionGrid.swift b/Session/Open Groups/OpenGroupSuggestionGrid.swift index 28bbe31ee6..ddc4c72ba9 100644 --- a/Session/Open Groups/OpenGroupSuggestionGrid.swift +++ b/Session/Open Groups/OpenGroupSuggestionGrid.swift @@ -4,6 +4,7 @@ import Foundation import GRDB import Combine import NVActivityIndicatorView +import SessionNetworkingKit import SessionMessagingKit import SessionUIKit import SessionUtilitiesKit @@ -354,7 +355,7 @@ extension OpenGroupSuggestionGrid { snContentView.pin(to: self) } - fileprivate func update(with room: OpenGroupAPI.Room, openGroup: OpenGroup, using dependencies: Dependencies) { + fileprivate func update(with room: Network.SOGS.Room, openGroup: OpenGroup, using dependencies: Dependencies) { label.text = room.name let maybePath: String? = openGroup.displayPictureOriginalUrl @@ -380,7 +381,7 @@ extension OpenGroupSuggestionGrid { // MARK: - Delegate protocol OpenGroupSuggestionGridDelegate { - func join(_ room: OpenGroupAPI.Room) + func join(_ room: Network.SOGS.Room) } // MARK: - LastRowCenteredLayout diff --git a/Session/Path/PathStatusView.swift b/Session/Path/PathStatusView.swift index 907947605f..33108de585 100644 --- a/Session/Path/PathStatusView.swift +++ b/Session/Path/PathStatusView.swift @@ -3,7 +3,7 @@ import UIKit import Combine import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Path/PathVC.swift b/Session/Path/PathVC.swift index 4bf304de7a..2ed2fb61ad 100644 --- a/Session/Path/PathVC.swift +++ b/Session/Path/PathVC.swift @@ -5,7 +5,7 @@ import Combine import NVActivityIndicatorView import SessionMessagingKit import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit final class PathVC: BaseVC { diff --git a/Session/Settings/AppIconViewModel.swift b/Session/Settings/AppIconViewModel.swift index c0cf0f2da8..5ebefe1e55 100644 --- a/Session/Settings/AppIconViewModel.swift +++ b/Session/Settings/AppIconViewModel.swift @@ -152,9 +152,11 @@ class AppIconViewModel: SessionTableViewModel, NavigatableStateHolder, Observabl oldValue: (previous != nil) ), onTap: { [weak self] in + let lastSelected: String? = dependencies[defaults: .standard, key: .lastSelectedAppIconDisguise] + switch current { case .some: self?.updateAppIcon(nil) - case .none: self?.updateAppIcon(.weather) + case .none: self?.updateAppIcon(lastSelected.map { AppIcon(name: $0) } ?? .weather) } } ) @@ -188,5 +190,11 @@ class AppIconViewModel: SessionTableViewModel, NavigatableStateHolder, Observabl } selectedOptionsSubject.send(icon?.rawValue) + + // Only store custom icons + if let currentIconName = icon?.rawValue { + // Save latest app icon disguise selected + dependencies[defaults: .standard, key: .lastSelectedAppIconDisguise] = currentIconName + } } } diff --git a/Session/Settings/AppearanceViewModel.swift b/Session/Settings/AppearanceViewModel.swift index 6f93c3f507..7f474ee65c 100644 --- a/Session/Settings/AppearanceViewModel.swift +++ b/Session/Settings/AppearanceViewModel.swift @@ -40,7 +40,7 @@ class AppearanceViewModel: SessionTableViewModel, NavigatableStateHolder, Observ case .themes: return "appearanceThemes".localized() case .primaryColor: return "appearancePrimaryColor".localized() case .primaryColorSelection: return nil - case .autoDarkMode: return "appearanceAutoDarkMode".localized() + case .autoDarkMode: return "darkMode".localized() case .appIcon: return "appIcon".localized() } } @@ -213,16 +213,16 @@ class AppearanceViewModel: SessionTableViewModel, NavigatableStateHolder, Observ elements: [ SessionCell.Info( id: .darkModeMatchSystemSettings, - title: SessionCell.TextInfo( - "followSystemSettings".localized(), - font: .titleRegular - ), + title: "appearanceAutoDarkMode".localized(), + subtitle: "followSystemSettings".localized(), trailingAccessory: .toggle( state.autoDarkModeEnabled, oldValue: previousState.autoDarkModeEnabled ), onTap: { ThemeManager.updateThemeState( + theme: state.theme, /// Keep the current value + primaryColor: state.primaryColor, /// Keep the current value matchSystemNightModeSetting: !state.autoDarkModeEnabled ) } @@ -238,7 +238,10 @@ class AppearanceViewModel: SessionTableViewModel, NavigatableStateHolder, Observ "appIconSelect".localized(), font: .titleRegular ), - trailingAccessory: .icon(.chevronRight), + trailingAccessory: .icon( + .chevronRight, + pinEdges: [.right] + ), onTap: { [weak viewModel, dependencies = viewModel.dependencies] in viewModel?.transitionToScreen( SessionTableViewController( diff --git a/Session/Settings/ConversationSettingsViewModel.swift b/Session/Settings/ConversationSettingsViewModel.swift index 0419aa80fc..5b52afca21 100644 --- a/Session/Settings/ConversationSettingsViewModel.swift +++ b/Session/Settings/ConversationSettingsViewModel.swift @@ -39,16 +39,11 @@ class ConversationSettingsViewModel: SessionTableViewModel, NavigatableStateHold switch self { case .messageTrimming: return "conversationsMessageTrimming".localized() case .audioMessages: return "conversationsAudioMessages".localized() - case .blockedContacts: return nil + case .blockedContacts: return "conversationsBlockedContacts".localized() } } - var style: SessionTableSectionStyle { - switch self { - case .blockedContacts: return .padding - default: return .titleRoundedContent - } - } + var style: SessionTableSectionStyle { .titleRoundedContent } } // MARK: - Content @@ -192,9 +187,13 @@ class ConversationSettingsViewModel: SessionTableViewModel, NavigatableStateHold SessionCell.Info( id: .blockedContacts, title: "conversationsBlockedContacts".localized(), - styling: SessionCell.StyleInfo( - tintColor: .danger, - backgroundStyle: .noBackground + subtitle: "blockedContactsManageDescription".localized(), + trailingAccessory: .icon( + .chevronRight, + pinEdges: [.right] + ), + accessibility: Accessibility( + identifier: "Block contacts - Navigation" ), onTap: { [weak viewModel, dependencies = viewModel.dependencies] in viewModel?.transitionToScreen( diff --git a/Session/Settings/DeveloperSettings/DeveloperSettingsGroupsViewModel.swift b/Session/Settings/DeveloperSettings/DeveloperSettingsGroupsViewModel.swift new file mode 100644 index 0000000000..61d62d1034 --- /dev/null +++ b/Session/Settings/DeveloperSettings/DeveloperSettingsGroupsViewModel.swift @@ -0,0 +1,391 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import Combine +import GRDB +import DifferenceKit +import SessionUIKit +import SessionNetworkingKit +import SessionMessagingKit +import SessionUtilitiesKit + +class DeveloperSettingsGroupsViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { + public let dependencies: Dependencies + public let navigatableState: NavigatableState = NavigatableState() + public let state: TableDataState = TableDataState() + public let observableState: ObservableTableSourceState = ObservableTableSourceState() + + /// This value is the current state of the view + @MainActor @Published private(set) var internalState: State + private var observationTask: Task? + + // MARK: - Initialization + + @MainActor init(using dependencies: Dependencies) { + self.dependencies = dependencies + self.internalState = State.initialState(using: dependencies) + + /// Bind the state + self.observationTask = ObservationBuilder + .initialValue(self.internalState) + .debounce(for: .never) + .using(dependencies: dependencies) + .query(DeveloperSettingsGroupsViewModel.queryState) + .assign { [weak self] updatedState in + guard let self = self else { return } + + // FIXME: To slightly reduce the size of the changes this new observation mechanism is currently wired into the old SessionTableViewController observation mechanism, we should refactor it so everything uses the new mechanism + let oldState: State = self.internalState + self.internalState = updatedState + self.pendingTableDataSubject.send(updatedState.sections(viewModel: self, previousState: oldState)) + } + } + + // MARK: - Config + + public enum Section: SessionTableSection { + case general + + var title: String? { + switch self { + case .general: return nil + } + } + + var style: SessionTableSectionStyle { + switch self { + case .general: return .padding + } + } + } + + public enum TableItem: Hashable, Differentiable, CaseIterable { + case updatedGroupsDisableAutoApprove + case updatedGroupsRemoveMessagesOnKick + case updatedGroupsAllowHistoricAccessOnInvite + case updatedGroupsAllowDisplayPicture + case updatedGroupsAllowDescriptionEditing + case updatedGroupsAllowPromotions + case updatedGroupsAllowInviteById + case updatedGroupsDeleteBeforeNow + case updatedGroupsDeleteAttachmentsBeforeNow + + // MARK: - Conformance + + public typealias DifferenceIdentifier = String + + public var differenceIdentifier: String { + switch self { + case .updatedGroupsDisableAutoApprove: return "updatedGroupsDisableAutoApprove" + case .updatedGroupsRemoveMessagesOnKick: return "updatedGroupsRemoveMessagesOnKick" + case .updatedGroupsAllowHistoricAccessOnInvite: return "updatedGroupsAllowHistoricAccessOnInvite" + case .updatedGroupsAllowDisplayPicture: return "updatedGroupsAllowDisplayPicture" + case .updatedGroupsAllowDescriptionEditing: return "updatedGroupsAllowDescriptionEditing" + case .updatedGroupsAllowPromotions: return "updatedGroupsAllowPromotions" + case .updatedGroupsAllowInviteById: return "updatedGroupsAllowInviteById" + case .updatedGroupsDeleteBeforeNow: return "updatedGroupsDeleteBeforeNow" + case .updatedGroupsDeleteAttachmentsBeforeNow: return "updatedGroupsDeleteAttachmentsBeforeNow" + } + } + + public func isContentEqual(to source: TableItem) -> Bool { + self.differenceIdentifier == source.differenceIdentifier + } + + public static var allCases: [TableItem] { + var result: [TableItem] = [] + switch TableItem.updatedGroupsDisableAutoApprove { + case .updatedGroupsDisableAutoApprove: result.append(.updatedGroupsDisableAutoApprove); fallthrough + case .updatedGroupsRemoveMessagesOnKick: result.append(.updatedGroupsRemoveMessagesOnKick); fallthrough + case .updatedGroupsAllowHistoricAccessOnInvite: + result.append(.updatedGroupsAllowHistoricAccessOnInvite); fallthrough + case .updatedGroupsAllowDisplayPicture: result.append(.updatedGroupsAllowDisplayPicture); fallthrough + case .updatedGroupsAllowDescriptionEditing: result.append(.updatedGroupsAllowDescriptionEditing); fallthrough + case .updatedGroupsAllowPromotions: result.append(.updatedGroupsAllowPromotions); fallthrough + case .updatedGroupsAllowInviteById: result.append(.updatedGroupsAllowInviteById); fallthrough + case .updatedGroupsDeleteBeforeNow: result.append(.updatedGroupsDeleteBeforeNow); fallthrough + case .updatedGroupsDeleteAttachmentsBeforeNow: result.append(.updatedGroupsDeleteAttachmentsBeforeNow) + } + + return result + } + } + + // MARK: - Content + + public struct State: Equatable, ObservableKeyProvider { + let updatedGroupsDisableAutoApprove: Bool + let updatedGroupsRemoveMessagesOnKick: Bool + let updatedGroupsAllowHistoricAccessOnInvite: Bool + let updatedGroupsAllowDisplayPicture: Bool + let updatedGroupsAllowDescriptionEditing: Bool + let updatedGroupsAllowPromotions: Bool + let updatedGroupsAllowInviteById: Bool + let updatedGroupsDeleteBeforeNow: Bool + let updatedGroupsDeleteAttachmentsBeforeNow: Bool + + @MainActor public func sections(viewModel: DeveloperSettingsGroupsViewModel, previousState: State) -> [SectionModel] { + DeveloperSettingsGroupsViewModel.sections( + state: self, + previousState: previousState, + viewModel: viewModel + ) + } + + public let observedKeys: Set = [ + .feature(.updatedGroupsDisableAutoApprove), + .feature(.updatedGroupsRemoveMessagesOnKick), + .feature(.updatedGroupsAllowHistoricAccessOnInvite), + .feature(.updatedGroupsAllowDisplayPicture), + .feature(.updatedGroupsAllowDescriptionEditing), + .feature(.updatedGroupsAllowPromotions), + .feature(.updatedGroupsAllowInviteById), + .feature(.updatedGroupsDeleteBeforeNow), + .feature(.updatedGroupsDeleteAttachmentsBeforeNow) + ] + + static func initialState(using dependencies: Dependencies) -> State { + return State( + updatedGroupsDisableAutoApprove: dependencies[feature: .updatedGroupsDisableAutoApprove], + updatedGroupsRemoveMessagesOnKick: dependencies[feature: .updatedGroupsRemoveMessagesOnKick], + updatedGroupsAllowHistoricAccessOnInvite: dependencies[feature: .updatedGroupsAllowHistoricAccessOnInvite], + updatedGroupsAllowDisplayPicture: dependencies[feature: .updatedGroupsAllowDisplayPicture], + updatedGroupsAllowDescriptionEditing: dependencies[feature: .updatedGroupsAllowDescriptionEditing], + updatedGroupsAllowPromotions: dependencies[feature: .updatedGroupsAllowPromotions], + updatedGroupsAllowInviteById: dependencies[feature: .updatedGroupsAllowInviteById], + updatedGroupsDeleteBeforeNow: dependencies[feature: .updatedGroupsDeleteBeforeNow], + updatedGroupsDeleteAttachmentsBeforeNow: dependencies[feature: .updatedGroupsDeleteAttachmentsBeforeNow] + ) + } + } + + let title: String = "Developer Group Settings" + + @Sendable private static func queryState( + previousState: State, + events: [ObservedEvent], + isInitialQuery: Bool, + using dependencies: Dependencies + ) async -> State { + return State( + updatedGroupsDisableAutoApprove: dependencies[feature: .updatedGroupsDisableAutoApprove], + updatedGroupsRemoveMessagesOnKick: dependencies[feature: .updatedGroupsRemoveMessagesOnKick], + updatedGroupsAllowHistoricAccessOnInvite: dependencies[feature: .updatedGroupsAllowHistoricAccessOnInvite], + updatedGroupsAllowDisplayPicture: dependencies[feature: .updatedGroupsAllowDisplayPicture], + updatedGroupsAllowDescriptionEditing: dependencies[feature: .updatedGroupsAllowDescriptionEditing], + updatedGroupsAllowPromotions: dependencies[feature: .updatedGroupsAllowPromotions], + updatedGroupsAllowInviteById: dependencies[feature: .updatedGroupsAllowInviteById], + updatedGroupsDeleteBeforeNow: dependencies[feature: .updatedGroupsDeleteBeforeNow], + updatedGroupsDeleteAttachmentsBeforeNow: dependencies[feature: .updatedGroupsDeleteAttachmentsBeforeNow] + ) + } + + private static func sections( + state: State, + previousState: State, + viewModel: DeveloperSettingsGroupsViewModel + ) -> [SectionModel] { + let general: SectionModel = SectionModel( + model: .general, + elements: [ + SessionCell.Info( + id: .updatedGroupsDisableAutoApprove, + title: "Disable Auto Approve", + subtitle: """ + Prevents a group from automatically getting approved if the admin is already approved. + + Note: The default behaviour is to automatically approve new groups if the admin that sent the invitation is an approved contact. + """, + trailingAccessory: .toggle( + state.updatedGroupsDisableAutoApprove, + oldValue: previousState.updatedGroupsDisableAutoApprove + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsDisableAutoApprove, + to: !state.updatedGroupsDisableAutoApprove + ) + } + ), + SessionCell.Info( + id: .updatedGroupsRemoveMessagesOnKick, + title: "Remove Messages on Kick", + subtitle: """ + Controls whether a group members messages should be removed when they are kicked from an updated group. + + Note: In a future release we will offer this as an option when removing members but for the initial release it can be controlled via this flag for testing purposes. + """, + trailingAccessory: .toggle( + state.updatedGroupsRemoveMessagesOnKick, + oldValue: previousState.updatedGroupsRemoveMessagesOnKick + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsRemoveMessagesOnKick, + to: !state.updatedGroupsRemoveMessagesOnKick + ) + } + ), + SessionCell.Info( + id: .updatedGroupsAllowHistoricAccessOnInvite, + title: "Allow Historic Message Access", + subtitle: """ + Controls whether members should be granted access to historic messages when invited to an updated group. + + Note: In a future release we will offer this as an option when inviting members but for the initial release it can be controlled via this flag for testing purposes. + """, + trailingAccessory: .toggle( + state.updatedGroupsAllowHistoricAccessOnInvite, + oldValue: previousState.updatedGroupsAllowHistoricAccessOnInvite + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsAllowHistoricAccessOnInvite, + to: !state.updatedGroupsAllowHistoricAccessOnInvite + ) + } + ), + SessionCell.Info( + id: .updatedGroupsAllowDisplayPicture, + title: "Custom Display Pictures", + subtitle: """ + Controls whether the UI allows group admins to set a custom display picture for a group. + + Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. + """, + trailingAccessory: .toggle( + state.updatedGroupsAllowDisplayPicture, + oldValue: previousState.updatedGroupsAllowDisplayPicture + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsAllowDisplayPicture, + to: !state.updatedGroupsAllowDisplayPicture + ) + } + ), + SessionCell.Info( + id: .updatedGroupsAllowDescriptionEditing, + title: "Edit Group Descriptions", + subtitle: """ + Controls whether the UI allows group admins to modify the descriptions of updated groups. + + Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. + """, + trailingAccessory: .toggle( + state.updatedGroupsAllowDescriptionEditing, + oldValue: previousState.updatedGroupsAllowDescriptionEditing + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsAllowDescriptionEditing, + to: !state.updatedGroupsAllowDescriptionEditing + ) + } + ), + SessionCell.Info( + id: .updatedGroupsAllowPromotions, + title: "Allow Group Promotions", + subtitle: """ + Controls whether the UI allows group admins to promote other group members to admin within an updated group. + + Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. + """, + trailingAccessory: .toggle( + state.updatedGroupsAllowPromotions, + oldValue: previousState.updatedGroupsAllowPromotions + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsAllowPromotions, + to: !state.updatedGroupsAllowPromotions + ) + } + ), + SessionCell.Info( + id: .updatedGroupsAllowInviteById, + title: "Allow Invite by ID", + subtitle: """ + Controls whether the UI allows group admins to invite other group members directly by their Account ID. + + Note: In a future release we will offer this functionality but it's not included in the initial release. + """, + trailingAccessory: .toggle( + state.updatedGroupsAllowInviteById, + oldValue: previousState.updatedGroupsAllowInviteById + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsAllowInviteById, + to: !state.updatedGroupsAllowInviteById + ) + } + ), + SessionCell.Info( + id: .updatedGroupsDeleteBeforeNow, + title: "Show button to delete messages before now", + subtitle: """ + Controls whether the UI allows group admins to delete all messages in the group that were sent before the button was pressed. + + Note: In a future release we will offer this functionality but it's not included in the initial release. + """, + trailingAccessory: .toggle( + state.updatedGroupsDeleteBeforeNow, + oldValue: previousState.updatedGroupsDeleteBeforeNow + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsDeleteBeforeNow, + to: !state.updatedGroupsDeleteBeforeNow + ) + } + ), + SessionCell.Info( + id: .updatedGroupsDeleteAttachmentsBeforeNow, + title: "Show button to delete attachments before now", + subtitle: """ + Controls whether the UI allows group admins to delete all attachments (and their associated messages) in the group that were sent before the button was pressed. + + Note: In a future release we will offer this functionality but it's not included in the initial release. + """, + trailingAccessory: .toggle( + state.updatedGroupsDeleteAttachmentsBeforeNow, + oldValue: previousState.updatedGroupsDeleteAttachmentsBeforeNow + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .updatedGroupsDeleteAttachmentsBeforeNow, + to: !state.updatedGroupsDeleteAttachmentsBeforeNow + ) + } + ) + ] + ) + + return [general] + } + + // MARK: - Functions + + public static func disableDeveloperMode(using dependencies: Dependencies) { + let features: [FeatureConfig] = [ + .updatedGroupsDisableAutoApprove, + .updatedGroupsRemoveMessagesOnKick, + .updatedGroupsAllowHistoricAccessOnInvite, + .updatedGroupsAllowDisplayPicture, + .updatedGroupsAllowDescriptionEditing, + .updatedGroupsAllowPromotions, + .updatedGroupsAllowInviteById, + .updatedGroupsDeleteBeforeNow, + .updatedGroupsDeleteAttachmentsBeforeNow + ] + + features.forEach { feature in + guard dependencies.hasSet(feature: feature) else { return } + + dependencies.set(feature: feature, to: nil) + } + } +} diff --git a/Session/Settings/DeveloperSettings/DeveloperSettingsProViewModel.swift b/Session/Settings/DeveloperSettings/DeveloperSettingsProViewModel.swift new file mode 100644 index 0000000000..0eb37764ab --- /dev/null +++ b/Session/Settings/DeveloperSettings/DeveloperSettingsProViewModel.swift @@ -0,0 +1,439 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import StoreKit +import Combine +import GRDB +import DifferenceKit +import SessionUIKit +import SessionNetworkingKit +import SessionMessagingKit +import SessionUtilitiesKit + +class DeveloperSettingsProViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource { + public let dependencies: Dependencies + public let navigatableState: NavigatableState = NavigatableState() + public let state: TableDataState = TableDataState() + public let observableState: ObservableTableSourceState = ObservableTableSourceState() + + /// This value is the current state of the view + @MainActor @Published private(set) var internalState: State + private var observationTask: Task? + + // MARK: - Initialization + + @MainActor init(using dependencies: Dependencies) { + self.dependencies = dependencies + self.internalState = State.initialState(using: dependencies) + + /// Bind the state + self.observationTask = ObservationBuilder + .initialValue(self.internalState) + .debounce(for: .never) + .using(dependencies: dependencies) + .query(DeveloperSettingsProViewModel.queryState) + .assign { [weak self] updatedState in + guard let self = self else { return } + + // FIXME: To slightly reduce the size of the changes this new observation mechanism is currently wired into the old SessionTableViewController observation mechanism, we should refactor it so everything uses the new mechanism + let oldState: State = self.internalState + self.internalState = updatedState + self.pendingTableDataSubject.send(updatedState.sections(viewModel: self, previousState: oldState)) + } + } + + // MARK: - Config + + public enum Section: SessionTableSection { + case general + case subscriptions + case features + + var title: String? { + switch self { + case .general: return nil + case .subscriptions: return "Subscriptions" + case .features: return "Features" + } + } + + var style: SessionTableSectionStyle { + switch self { + case .general: return .padding + default: return .titleRoundedContent + } + } + } + + public enum TableItem: Hashable, Differentiable, CaseIterable { + case enableSessionPro + + case purchaseProSubscription + case manageProSubscriptions + case restoreProSubscription + + case proStatus + case proIncomingMessages + + // MARK: - Conformance + + public typealias DifferenceIdentifier = String + + public var differenceIdentifier: String { + switch self { + case .enableSessionPro: return "enableSessionPro" + + case .purchaseProSubscription: return "purchaseProSubscription" + case .manageProSubscriptions: return "manageProSubscriptions" + case .restoreProSubscription: return "restoreProSubscription" + + case .proStatus: return "proStatus" + case .proIncomingMessages: return "proIncomingMessages" + } + } + + public func isContentEqual(to source: TableItem) -> Bool { + self.differenceIdentifier == source.differenceIdentifier + } + + public static var allCases: [TableItem] { + var result: [TableItem] = [] + switch TableItem.enableSessionPro { + case .enableSessionPro: result.append(.enableSessionPro); fallthrough + + case .purchaseProSubscription: result.append(.purchaseProSubscription); fallthrough + case .manageProSubscriptions: result.append(.manageProSubscriptions); fallthrough + case .restoreProSubscription: result.append(.restoreProSubscription); fallthrough + + case .proStatus: result.append(.proStatus); fallthrough + case .proIncomingMessages: result.append(.proIncomingMessages) + } + + return result + } + } + + public enum DeveloperSettingsProEvent: Hashable { + case purchasedProduct([Product], Product?, String?, String?, UInt64?) + } + + // MARK: - Content + + public struct State: Equatable, ObservableKeyProvider { + let sessionProEnabled: Bool + + let products: [Product] + let purchasedProduct: Product? + let purchaseError: String? + let purchaseStatus: String? + let purchaseTransactionId: String? + + let mockCurrentUserSessionPro: Bool + let treatAllIncomingMessagesAsProMessages: Bool + + @MainActor public func sections(viewModel: DeveloperSettingsProViewModel, previousState: State) -> [SectionModel] { + DeveloperSettingsProViewModel.sections( + state: self, + previousState: previousState, + viewModel: viewModel + ) + } + + public let observedKeys: Set = [ + .feature(.sessionProEnabled), + .updateScreen(DeveloperSettingsProViewModel.self), + .feature(.mockCurrentUserSessionPro), + .feature(.treatAllIncomingMessagesAsProMessages) + ] + + static func initialState(using dependencies: Dependencies) -> State { + return State( + sessionProEnabled: dependencies[feature: .sessionProEnabled], + + products: [], + purchasedProduct: nil, + purchaseError: nil, + purchaseStatus: nil, + purchaseTransactionId: nil, + + mockCurrentUserSessionPro: dependencies[feature: .mockCurrentUserSessionPro], + treatAllIncomingMessagesAsProMessages: dependencies[feature: .treatAllIncomingMessagesAsProMessages] + ) + } + } + + let title: String = "Developer Pro Settings" + + @Sendable private static func queryState( + previousState: State, + events: [ObservedEvent], + isInitialQuery: Bool, + using dependencies: Dependencies + ) async -> State { + var products: [Product] = previousState.products + var purchasedProduct: Product? = previousState.purchasedProduct + var purchaseError: String? = previousState.purchaseError + var purchaseStatus: String? = previousState.purchaseStatus + var purchaseTransactionId: String? = previousState.purchaseTransactionId + + events.forEach { event in + guard let eventValue: DeveloperSettingsProEvent = event.value as? DeveloperSettingsProEvent else { return } + + switch eventValue { + case .purchasedProduct(let receivedProducts, let purchased, let error, let status, let id): + products = receivedProducts + purchasedProduct = purchased + purchaseError = error + purchaseStatus = status + purchaseTransactionId = id.map { "\($0)" } + } + } + + return State( + sessionProEnabled: dependencies[feature: .sessionProEnabled], + products: products, + purchasedProduct: purchasedProduct, + purchaseError: purchaseError, + purchaseStatus: purchaseStatus, + purchaseTransactionId: purchaseTransactionId, + mockCurrentUserSessionPro: dependencies[feature: .mockCurrentUserSessionPro], + treatAllIncomingMessagesAsProMessages: dependencies[feature: .treatAllIncomingMessagesAsProMessages] + ) + } + + private static func sections( + state: State, + previousState: State, + viewModel: DeveloperSettingsProViewModel + ) -> [SectionModel] { + let general: SectionModel = SectionModel( + model: .general, + elements: [ + SessionCell.Info( + id: .enableSessionPro, + title: "Enable Session Pro", + subtitle: """ + Enable Post Pro Release mode. + Turning on this Settings will show Pro badge and CTA if needed. + """, + trailingAccessory: .toggle( + state.sessionProEnabled, + oldValue: previousState.sessionProEnabled + ), + onTap: { [weak viewModel] in + viewModel?.updateSessionProEnabled(current: state.sessionProEnabled) + } + ) + ] + ) + + guard state.sessionProEnabled else { return [general] } + + let purchaseStatus: String = { + switch (state.purchaseError, state.purchaseStatus) { + case (.some(let error), _): return "\(error)" + case (_, .some(let status)): return "\(status)" + case (.none, .none): return "None" + } + }() + let productName: String = ( + state.purchasedProduct.map { "\($0.displayName)" } ?? + "N/A" + ) + let transactionId: String = ( + state.purchaseTransactionId.map { "\($0)" } ?? + "N/A" + ) + let subscriptions: SectionModel = SectionModel( + model: .subscriptions, + elements: [ + SessionCell.Info( + id: .purchaseProSubscription, + title: "Purchase Subscription", + subtitle: """ + Purchase Session Pro via the App Store. + + Status: \(purchaseStatus) + Product Name: \(productName) + TransactionId: \(transactionId) + """, + trailingAccessory: .highlightingBackgroundLabel(title: "Purchase"), + onTap: { [weak viewModel] in + Task { await viewModel?.purchaseSubscription() } + } + ), + SessionCell.Info( + id: .manageProSubscriptions, + title: "Manage Subscriptions", + subtitle: """ + Manage subscriptions for Session Pro via the App Store. + + Note: You must purchase a Session Pro subscription before you can manage it. + """, + trailingAccessory: .highlightingBackgroundLabel(title: "Manage"), + onTap: { [weak viewModel] in + Task { await viewModel?.manageSubscriptions() } + } + ), + SessionCell.Info( + id: .restoreProSubscription, + title: "Restore Subscriptions", + subtitle: """ + Restore a Session Pro subscription via the App Store. + """, + trailingAccessory: .highlightingBackgroundLabel(title: "Restore"), + onTap: { [weak viewModel] in + Task { await viewModel?.restoreSubscriptions() } + } + ) + ] + ) + + let features: SectionModel = SectionModel( + model: .features, + elements: [ + SessionCell.Info( + id: .proStatus, + title: "Pro Status", + subtitle: """ + Mock current user a Session Pro user locally. + """, + trailingAccessory: .toggle( + state.mockCurrentUserSessionPro, + oldValue: previousState.mockCurrentUserSessionPro + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .mockCurrentUserSessionPro, + to: !state.mockCurrentUserSessionPro + ) + } + ), + SessionCell.Info( + id: .proIncomingMessages, + title: "All Pro Incoming Messages", + subtitle: """ + Treat all incoming messages as Pro messages. + """, + trailingAccessory: .toggle( + state.treatAllIncomingMessagesAsProMessages, + oldValue: previousState.treatAllIncomingMessagesAsProMessages + ), + onTap: { [dependencies = viewModel.dependencies] in + dependencies.set( + feature: .treatAllIncomingMessagesAsProMessages, + to: !state.treatAllIncomingMessagesAsProMessages + ) + } + ) + ] + ) + + return [general, subscriptions, features] + } + + // MARK: - Functions + + public static func disableDeveloperMode(using dependencies: Dependencies) { + let features: [FeatureConfig] = [ + .sessionProEnabled, + .mockCurrentUserSessionPro, + .treatAllIncomingMessagesAsProMessages + ] + + features.forEach { feature in + guard dependencies.hasSet(feature: feature) else { return } + + dependencies.set(feature: feature, to: nil) + } + } + + private func updateSessionProEnabled(current: Bool) { + dependencies.set(feature: .sessionProEnabled, to: !current) + + if dependencies.hasSet(feature: .mockCurrentUserSessionPro) { + dependencies.set(feature: .mockCurrentUserSessionPro, to: nil) + } + + if dependencies.hasSet(feature: .treatAllIncomingMessagesAsProMessages) { + dependencies.set(feature: .treatAllIncomingMessagesAsProMessages, to: nil) + } + } + + private func purchaseSubscription() async { + do { + let products: [Product] = try await Product.products(for: ["com.getsession.org.pro_sub"]) + + guard let product: Product = products.first else { + Log.error("[DevSettings] Unable to purchase subscription due to error: No products found") + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct([], nil, "No products found", nil, nil) + ) + return + } + + let result = try await product.purchase() + switch result { + case .success(let verificationResult): + let transaction = try verificationResult.payloadValue + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct(products, product, nil, "Successful", transaction.id) + ) + await transaction.finish() + + case .pending: + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct(products, product, nil, "Pending approval", nil) + ) + + case .userCancelled: + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct(products, product, nil, "User cancelled", nil) + ) + + @unknown default: + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct(products, product, "Unknown Error", nil, nil) + ) + } + + } + catch { + Log.error("[DevSettings] Unable to purchase subscription due to error: \(error)") + dependencies.notifyAsync( + key: .updateScreen(DeveloperSettingsProViewModel.self), + value: DeveloperSettingsProEvent.purchasedProduct([], nil, "Failed: \(error)", nil, nil) + ) + } + } + + private func manageSubscriptions() async { + guard let scene: UIWindowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { + return Log.error("[DevSettings] Unable to show manage subscriptions: Unable to get UIWindowScene") + } + + do { + try await AppStore.showManageSubscriptions(in: scene) + print("AS") + } + catch { + Log.error("[DevSettings] Unable to show manage subscriptions: \(error)") + } + } + + private func restoreSubscriptions() async { + do { + try await AppStore.sync() + } + catch { + Log.error("[DevSettings] Unable to show manage subscriptions: \(error)") + } + } +} diff --git a/Session/Settings/DeveloperSettingsViewModel+Testing.swift b/Session/Settings/DeveloperSettings/DeveloperSettingsViewModel+Testing.swift similarity index 100% rename from Session/Settings/DeveloperSettingsViewModel+Testing.swift rename to Session/Settings/DeveloperSettings/DeveloperSettingsViewModel+Testing.swift diff --git a/Session/Settings/DeveloperSettingsViewModel.swift b/Session/Settings/DeveloperSettings/DeveloperSettingsViewModel.swift similarity index 78% rename from Session/Settings/DeveloperSettingsViewModel.swift rename to Session/Settings/DeveloperSettings/DeveloperSettingsViewModel.swift index 36cf898bec..b32ecb10ad 100644 --- a/Session/Settings/DeveloperSettingsViewModel.swift +++ b/Session/Settings/DeveloperSettings/DeveloperSettingsViewModel.swift @@ -8,7 +8,7 @@ import Compression import GRDB import DifferenceKit import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit import SignalUtilitiesKit @@ -35,27 +35,27 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, public enum Section: SessionTableSection { case developerMode - case sessionPro case sessionNetwork + case sessionPro + case groups case general case logging case network case disappearingMessages case communities - case groups case database var title: String? { switch self { case .developerMode: return nil - case .sessionPro: return "Session Pro" case .sessionNetwork: return "Session Network" + case .sessionPro: return "Session Pro" + case .groups: return "Groups" case .general: return "General" case .logging: return "Logging" case .network: return "Network" case .disappearingMessages: return "Disappearing Messages" case .communities: return "Communities" - case .groups: return "Groups" case .database: return "Database" } } @@ -71,12 +71,8 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, public enum TableItem: Hashable, Differentiable, CaseIterable { case developerMode - case enableSessionPro - case proStatus - case proIncomingMessages - - case versionBlindedID - case scheduleLocalNotification + case proConfig + case groupConfig case animationsEnabled case showStringKeys @@ -99,15 +95,9 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, case communityPollLimit - case updatedGroupsDisableAutoApprove - case updatedGroupsRemoveMessagesOnKick - case updatedGroupsAllowHistoricAccessOnInvite - case updatedGroupsAllowDisplayPicture - case updatedGroupsAllowDescriptionEditing - case updatedGroupsAllowPromotions - case updatedGroupsAllowInviteById - case updatedGroupsDeleteBeforeNow - case updatedGroupsDeleteAttachmentsBeforeNow + + case versionBlindedID + case scheduleLocalNotification case createMockContacts case forceSlowDatabaseQueries @@ -121,6 +111,10 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, public var differenceIdentifier: String { switch self { case .developerMode: return "developerMode" + + case .proConfig: return "proConfig" + case .groupConfig: return "groupConfig" + case .animationsEnabled: return "animationsEnabled" case .showStringKeys: return "showStringKeys" case .truncatePubkeysInLogs: return "truncatePubkeysInLogs" @@ -142,22 +136,8 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, case .communityPollLimit: return "communityPollLimit" - case .updatedGroupsDisableAutoApprove: return "updatedGroupsDisableAutoApprove" - case .updatedGroupsRemoveMessagesOnKick: return "updatedGroupsRemoveMessagesOnKick" - case .updatedGroupsAllowHistoricAccessOnInvite: return "updatedGroupsAllowHistoricAccessOnInvite" - case .updatedGroupsAllowDisplayPicture: return "updatedGroupsAllowDisplayPicture" - case .updatedGroupsAllowDescriptionEditing: return "updatedGroupsAllowDescriptionEditing" - case .updatedGroupsAllowPromotions: return "updatedGroupsAllowPromotions" - case .updatedGroupsAllowInviteById: return "updatedGroupsAllowInviteById" - case .updatedGroupsDeleteBeforeNow: return "updatedGroupsDeleteBeforeNow" - case .updatedGroupsDeleteAttachmentsBeforeNow: return "updatedGroupsDeleteAttachmentsBeforeNow" - case .versionBlindedID: return "versionBlindedID" case .scheduleLocalNotification: return "scheduleLocalNotification" - - case .enableSessionPro: return "enableSessionPro" - case .proStatus: return "proStatus" - case .proIncomingMessages: return "proIncomingMessages" case .createMockContacts: return "createMockContacts" case .forceSlowDatabaseQueries: return "forceSlowDatabaseQueries" @@ -174,6 +154,10 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, var result: [TableItem] = [] switch TableItem.developerMode { case .developerMode: result.append(.developerMode); fallthrough + + case .proConfig: result.append(.proConfig); fallthrough + case .groupConfig: result.append(.groupConfig); fallthrough + case .animationsEnabled: result.append(.animationsEnabled); fallthrough case .showStringKeys: result.append(.showStringKeys); fallthrough case .truncatePubkeysInLogs: result.append(.truncatePubkeysInLogs); fallthrough @@ -195,24 +179,9 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, case .communityPollLimit: result.append(.communityPollLimit); fallthrough - case .updatedGroupsDisableAutoApprove: result.append(.updatedGroupsDisableAutoApprove); fallthrough - case .updatedGroupsRemoveMessagesOnKick: result.append(.updatedGroupsRemoveMessagesOnKick); fallthrough - case .updatedGroupsAllowHistoricAccessOnInvite: - result.append(.updatedGroupsAllowHistoricAccessOnInvite); fallthrough - case .updatedGroupsAllowDisplayPicture: result.append(.updatedGroupsAllowDisplayPicture); fallthrough - case .updatedGroupsAllowDescriptionEditing: result.append(.updatedGroupsAllowDescriptionEditing); fallthrough - case .updatedGroupsAllowPromotions: result.append(.updatedGroupsAllowPromotions); fallthrough - case .updatedGroupsAllowInviteById: result.append(.updatedGroupsAllowInviteById); fallthrough - case .updatedGroupsDeleteBeforeNow: result.append(.updatedGroupsDeleteBeforeNow); fallthrough - case .updatedGroupsDeleteAttachmentsBeforeNow: result.append(.updatedGroupsDeleteAttachmentsBeforeNow); fallthrough - case .versionBlindedID: result.append(.versionBlindedID); fallthrough case .scheduleLocalNotification: result.append(.scheduleLocalNotification); fallthrough - case .enableSessionPro: result.append(.enableSessionPro); fallthrough - case .proStatus: result.append(.proStatus); fallthrough - case .proIncomingMessages: result.append(.proIncomingMessages); fallthrough - case .createMockContacts: result.append(.createMockContacts); fallthrough case .forceSlowDatabaseQueries: result.append(.forceSlowDatabaseQueries); fallthrough case .exportDatabase: result.append(.exportDatabase); fallthrough @@ -239,26 +208,12 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, let serviceNetwork: ServiceNetwork let forceOffline: Bool - let pushNotificationService: PushNotificationAPI.Service + let pushNotificationService: Network.PushNotification.Service let debugDisappearingMessageDurations: Bool let communityPollLimit: Int - let updatedGroupsDisableAutoApprove: Bool - let updatedGroupsRemoveMessagesOnKick: Bool - let updatedGroupsAllowHistoricAccessOnInvite: Bool - let updatedGroupsAllowDisplayPicture: Bool - let updatedGroupsAllowDescriptionEditing: Bool - let updatedGroupsAllowPromotions: Bool - let updatedGroupsAllowInviteById: Bool - let updatedGroupsDeleteBeforeNow: Bool - let updatedGroupsDeleteAttachmentsBeforeNow: Bool - - let sessionProEnabled: Bool - let mockCurrentUserSessionPro: Bool - let treatAllIncomingMessagesAsProMessages: Bool - let forceSlowDatabaseQueries: Bool let updateSimulateAppReviewLimit: Bool @@ -302,20 +257,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, communityPollLimit: dependencies[feature: .communityPollLimit], - updatedGroupsDisableAutoApprove: dependencies[feature: .updatedGroupsDisableAutoApprove], - updatedGroupsRemoveMessagesOnKick: dependencies[feature: .updatedGroupsRemoveMessagesOnKick], - updatedGroupsAllowHistoricAccessOnInvite: dependencies[feature: .updatedGroupsAllowHistoricAccessOnInvite], - updatedGroupsAllowDisplayPicture: dependencies[feature: .updatedGroupsAllowDisplayPicture], - updatedGroupsAllowDescriptionEditing: dependencies[feature: .updatedGroupsAllowDescriptionEditing], - updatedGroupsAllowPromotions: dependencies[feature: .updatedGroupsAllowPromotions], - updatedGroupsAllowInviteById: dependencies[feature: .updatedGroupsAllowInviteById], - updatedGroupsDeleteBeforeNow: dependencies[feature: .updatedGroupsDeleteBeforeNow], - updatedGroupsDeleteAttachmentsBeforeNow: dependencies[feature: .updatedGroupsDeleteAttachmentsBeforeNow], - - sessionProEnabled: dependencies[feature: .sessionProEnabled], - mockCurrentUserSessionPro: dependencies[feature: .mockCurrentUserSessionPro], - treatAllIncomingMessagesAsProMessages: dependencies[feature: .treatAllIncomingMessagesAsProMessages], - forceSlowDatabaseQueries: dependencies[feature: .forceSlowDatabaseQueries], updateSimulateAppReviewLimit: dependencies[feature: .simulateAppReviewLimit] ) @@ -348,6 +289,48 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ) ] ) + let sessionPro: SectionModel = SectionModel( + model: .sessionPro, + elements: [ + SessionCell.Info( + id: .proConfig, + title: "Session Pro", + subtitle: """ + Configure settings related to Session Pro. + + Session Pro: \(dependencies[feature: .sessionProEnabled] ? "Enabled" : "Disabled") + """, + trailingAccessory: .icon(.chevronRight), + onTap: { [weak self, dependencies] in + self?.transitionToScreen( + SessionTableViewController( + viewModel: DeveloperSettingsProViewModel(using: dependencies) + ) + ) + } + ) + ] + ) + let groups: SectionModel = SectionModel( + model: .groups, + elements: [ + SessionCell.Info( + id: .groupConfig, + title: "Group Configuration", + subtitle: """ + Configure settings related to Groups. + """, + trailingAccessory: .icon(.chevronRight), + onTap: { [weak self, dependencies] in + self?.transitionToScreen( + SessionTableViewController( + viewModel: DeveloperSettingsGroupsViewModel(using: dependencies) + ) + ) + } + ) + ] + ) let general: SectionModel = SectionModel( model: .general, elements: [ @@ -594,9 +577,9 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, onTap: { [weak self, dependencies] in self?.transitionToScreen( SessionTableViewController( - viewModel: SessionListViewModel( + viewModel: SessionListViewModel( title: "Push Notification Service", - options: PushNotificationAPI.Service.allCases, + options: Network.PushNotification.Service.allCases, behaviour: .autoDismiss( initialSelection: current.pushNotificationService, onOptionSelected: self?.updatePushNotificationService @@ -659,182 +642,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ) ] ) - let groups: SectionModel = SectionModel( - model: .groups, - elements: [ - SessionCell.Info( - id: .updatedGroupsDisableAutoApprove, - title: "Disable Auto Approve", - subtitle: """ - Prevents a group from automatically getting approved if the admin is already approved. - - Note: The default behaviour is to automatically approve new groups if the admin that sent the invitation is an approved contact. - """, - trailingAccessory: .toggle( - current.updatedGroupsDisableAutoApprove, - oldValue: previous?.updatedGroupsDisableAutoApprove - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsDisableAutoApprove, - to: !current.updatedGroupsDisableAutoApprove - ) - } - ), - SessionCell.Info( - id: .updatedGroupsRemoveMessagesOnKick, - title: "Remove Messages on Kick", - subtitle: """ - Controls whether a group members messages should be removed when they are kicked from an updated group. - - Note: In a future release we will offer this as an option when removing members but for the initial release it can be controlled via this flag for testing purposes. - """, - trailingAccessory: .toggle( - current.updatedGroupsRemoveMessagesOnKick, - oldValue: previous?.updatedGroupsRemoveMessagesOnKick - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsRemoveMessagesOnKick, - to: !current.updatedGroupsRemoveMessagesOnKick - ) - } - ), - SessionCell.Info( - id: .updatedGroupsAllowHistoricAccessOnInvite, - title: "Allow Historic Message Access", - subtitle: """ - Controls whether members should be granted access to historic messages when invited to an updated group. - - Note: In a future release we will offer this as an option when inviting members but for the initial release it can be controlled via this flag for testing purposes. - """, - trailingAccessory: .toggle( - current.updatedGroupsAllowHistoricAccessOnInvite, - oldValue: previous?.updatedGroupsAllowHistoricAccessOnInvite - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsAllowHistoricAccessOnInvite, - to: !current.updatedGroupsAllowHistoricAccessOnInvite - ) - } - ), - SessionCell.Info( - id: .updatedGroupsAllowDisplayPicture, - title: "Custom Display Pictures", - subtitle: """ - Controls whether the UI allows group admins to set a custom display picture for a group. - - Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. - """, - trailingAccessory: .toggle( - current.updatedGroupsAllowDisplayPicture, - oldValue: previous?.updatedGroupsAllowDisplayPicture - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsAllowDisplayPicture, - to: !current.updatedGroupsAllowDisplayPicture - ) - } - ), - SessionCell.Info( - id: .updatedGroupsAllowDescriptionEditing, - title: "Edit Group Descriptions", - subtitle: """ - Controls whether the UI allows group admins to modify the descriptions of updated groups. - - Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. - """, - trailingAccessory: .toggle( - current.updatedGroupsAllowDescriptionEditing, - oldValue: previous?.updatedGroupsAllowDescriptionEditing - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsAllowDescriptionEditing, - to: !current.updatedGroupsAllowDescriptionEditing - ) - } - ), - SessionCell.Info( - id: .updatedGroupsAllowPromotions, - title: "Allow Group Promotions", - subtitle: """ - Controls whether the UI allows group admins to promote other group members to admin within an updated group. - - Note: In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes. - """, - trailingAccessory: .toggle( - current.updatedGroupsAllowPromotions, - oldValue: previous?.updatedGroupsAllowPromotions - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsAllowPromotions, - to: !current.updatedGroupsAllowPromotions - ) - } - ), - SessionCell.Info( - id: .updatedGroupsAllowInviteById, - title: "Allow Invite by ID", - subtitle: """ - Controls whether the UI allows group admins to invite other group members directly by their Account ID. - - Note: In a future release we will offer this functionality but it's not included in the initial release. - """, - trailingAccessory: .toggle( - current.updatedGroupsAllowInviteById, - oldValue: previous?.updatedGroupsAllowInviteById - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsAllowInviteById, - to: !current.updatedGroupsAllowInviteById - ) - } - ), - SessionCell.Info( - id: .updatedGroupsDeleteBeforeNow, - title: "Show button to delete messages before now", - subtitle: """ - Controls whether the UI allows group admins to delete all messages in the group that were sent before the button was pressed. - - Note: In a future release we will offer this functionality but it's not included in the initial release. - """, - trailingAccessory: .toggle( - current.updatedGroupsDeleteBeforeNow, - oldValue: previous?.updatedGroupsDeleteBeforeNow - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsDeleteBeforeNow, - to: !current.updatedGroupsDeleteBeforeNow - ) - } - ), - SessionCell.Info( - id: .updatedGroupsDeleteAttachmentsBeforeNow, - title: "Show button to delete attachments before now", - subtitle: """ - Controls whether the UI allows group admins to delete all attachments (and their associated messages) in the group that were sent before the button was pressed. - - Note: In a future release we will offer this functionality but it's not included in the initial release. - """, - trailingAccessory: .toggle( - current.updatedGroupsDeleteAttachmentsBeforeNow, - oldValue: previous?.updatedGroupsDeleteAttachmentsBeforeNow - ), - onTap: { [weak self] in - self?.updateFlag( - for: .updatedGroupsDeleteAttachmentsBeforeNow, - to: !current.updatedGroupsDeleteAttachmentsBeforeNow - ) - } - ) - ] - ) let database: SectionModel = SectionModel( model: .database, elements: [ @@ -898,63 +705,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ) ] ) - let sessionPro: SectionModel = SectionModel( - model: .sessionPro, - elements: [ - SessionCell.Info( - id: .enableSessionPro, - title: "Enable Session Pro", - subtitle: """ - Enable Post Pro Release mode. - Turning on this Settings will show Pro badge and CTA if needed. - """, - trailingAccessory: .toggle( - current.sessionProEnabled, - oldValue: previous?.sessionProEnabled - ), - onTap: { [weak self] in - self?.updateSessionProEnabled(current: current.sessionProEnabled) - } - ) - ].appending( - contentsOf: current.sessionProEnabled ? [ - SessionCell.Info( - id: .proStatus, - title: "Pro Status", - subtitle: """ - Mock current user a Session Pro user locally. - """, - trailingAccessory: .toggle( - current.mockCurrentUserSessionPro, - oldValue: previous?.mockCurrentUserSessionPro - ), - onTap: { [weak self] in - self?.updateFlag( - for: .mockCurrentUserSessionPro, - to: !current.mockCurrentUserSessionPro - ) - } - ), - SessionCell.Info( - id: .proIncomingMessages, - title: "All Pro Incoming Messages", - subtitle: """ - Treat all incoming messages as Pro messages. - """, - trailingAccessory: .toggle( - current.treatAllIncomingMessagesAsProMessages, - oldValue: previous?.treatAllIncomingMessagesAsProMessages - ), - onTap: { [weak self] in - self?.updateFlag( - for: .treatAllIncomingMessagesAsProMessages, - to: !current.treatAllIncomingMessagesAsProMessages - ) - } - ) - ] : nil - ) - ) let sessionNetwork: SectionModel = SectionModel( model: .sessionNetwork, elements: [ @@ -993,13 +743,13 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, return [ developerMode, + sessionPro, + groups, general, logging, network, disappearingMessages, communities, - groups, - sessionPro, sessionNetwork, database ] @@ -1012,9 +762,10 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, /// then we will get a compile error if it doesn't get resetting instructions added) TableItem.allCases.forEach { item in switch item { - case .developerMode: break // Not a feature - case .versionBlindedID: break // Not a feature - case .scheduleLocalNotification: break // Not a feature + case .developerMode, .versionBlindedID, .scheduleLocalNotification, .copyDocumentsPath, + .copyAppGroupPath, .resetSnodeCache, .createMockContacts, .exportDatabase, + .importDatabase, .advancedLogging, .resetAppReviewPrompt: + break /// These are actions rather than values stored as "features" so no need to do anything case .animationsEnabled: guard dependencies.hasSet(feature: .animationsEnabled) else { return } @@ -1031,18 +782,10 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, updateFlag(for: .truncatePubkeysInLogs, to: nil) - case .copyDocumentsPath: break // Not a feature - case .copyAppGroupPath: break // Not a feature - case .resetAppReviewPrompt: break case .simulateAppReviewLimit: guard dependencies.hasSet(feature: .simulateAppReviewLimit) else { return } updateFlag(for: .simulateAppReviewLimit, to: nil) - case .resetSnodeCache: break // Not a feature - case .createMockContacts: break // Not a feature - case .exportDatabase: break // Not a feature - case .importDatabase: break // Not a feature - case .advancedLogging: break // Not a feature case .defaultLogLevel: updateDefaulLogLevel(to: nil) // Always reset case .loggingCategory: resetLoggingCategories() // Always reset @@ -1073,71 +816,8 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, dependencies.set(feature: .communityPollLimit, to: nil) forceRefresh(type: .databaseQuery) - case .updatedGroupsDisableAutoApprove: - guard dependencies.hasSet(feature: .updatedGroupsDisableAutoApprove) else { return } - - updateFlag(for: .updatedGroupsDisableAutoApprove, to: nil) - - case .updatedGroupsRemoveMessagesOnKick: - guard dependencies.hasSet(feature: .updatedGroupsRemoveMessagesOnKick) else { return } - - updateFlag(for: .updatedGroupsRemoveMessagesOnKick, to: nil) - - case .updatedGroupsAllowHistoricAccessOnInvite: - guard dependencies.hasSet(feature: .updatedGroupsAllowHistoricAccessOnInvite) else { - return - } - - updateFlag(for: .updatedGroupsAllowHistoricAccessOnInvite, to: nil) - - case .updatedGroupsAllowDisplayPicture: - guard dependencies.hasSet(feature: .updatedGroupsAllowDisplayPicture) else { return } - - updateFlag(for: .updatedGroupsAllowDisplayPicture, to: nil) - - case .updatedGroupsAllowDescriptionEditing: - guard dependencies.hasSet(feature: .updatedGroupsAllowDescriptionEditing) else { return } - - updateFlag(for: .updatedGroupsAllowDescriptionEditing, to: nil) - - case .updatedGroupsAllowPromotions: - guard dependencies.hasSet(feature: .updatedGroupsAllowPromotions) else { return } - - updateFlag(for: .updatedGroupsAllowPromotions, to: nil) - - case .updatedGroupsAllowInviteById: - guard dependencies.hasSet(feature: .updatedGroupsAllowInviteById) else { return } - - updateFlag(for: .updatedGroupsAllowInviteById, to: nil) - - case .updatedGroupsDeleteBeforeNow: - guard dependencies.hasSet(feature: .updatedGroupsDeleteBeforeNow) else { return } - - updateFlag(for: .updatedGroupsDeleteBeforeNow, to: nil) - - case .updatedGroupsDeleteAttachmentsBeforeNow: - guard dependencies.hasSet(feature: .updatedGroupsDeleteAttachmentsBeforeNow) else { - return - } - - updateFlag(for: .updatedGroupsDeleteAttachmentsBeforeNow, to: nil) - - case .enableSessionPro: - guard dependencies.hasSet(feature: .sessionProEnabled) else { return } - - updateFlag(for: .sessionProEnabled, to: nil) - - case .proStatus: - guard dependencies.hasSet(feature: .mockCurrentUserSessionPro) else { return } - - updateFlag(for: .mockCurrentUserSessionPro, to: nil) - - case .proIncomingMessages: - guard dependencies.hasSet(feature: .treatAllIncomingMessagesAsProMessages) else { - return - } - - updateFlag(for: .treatAllIncomingMessagesAsProMessages, to: nil) + case .groupConfig: DeveloperSettingsGroupsViewModel.disableDeveloperMode(using: dependencies) + case .proConfig: DeveloperSettingsProViewModel.disableDeveloperMode(using: dependencies) case .forceSlowDatabaseQueries: guard dependencies.hasSet(feature: .forceSlowDatabaseQueries) else { return } @@ -1181,7 +861,7 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, forceRefresh(type: .databaseQuery) } - private func updatePushNotificationService(to updatedService: PushNotificationAPI.Service?) { + private func updatePushNotificationService(to updatedService: Network.PushNotification.Service?) { guard dependencies[defaults: .standard, key: .isUsingFullAPNs], updatedService != dependencies[feature: .pushNotificationService] @@ -1270,7 +950,7 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, /// Unsubscribe from push notifications (do this after resetting the network as they are server requests so aren't dependant on a service /// layer and we don't want these to be cancelled) if let existingToken: String = dependencies[singleton: .storage].read({ db in db[.lastRecordedPushToken] }) { - PushNotificationAPI + Network.PushNotification .unsubscribeAll(token: Data(hex: existingToken), using: dependencies) .sinkUntilComplete() } @@ -1347,16 +1027,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, forceRefresh(type: .databaseQuery) } - private func updateSessionProEnabled(current: Bool) { - updateFlag(for: .sessionProEnabled, to: !current) - if dependencies.hasSet(feature: .mockCurrentUserSessionPro) { - updateFlag(for: .mockCurrentUserSessionPro, to: nil) - } - if dependencies.hasSet(feature: .treatAllIncomingMessagesAsProMessages) { - updateFlag(for: .treatAllIncomingMessagesAsProMessages, to: nil) - } - } - private func updateForceOffline(current: Bool) { updateFlag(for: .forceOffline, to: !current) @@ -2104,9 +1774,9 @@ final class PollLimitInputView: UIView, UITextFieldDelegate, SessionCell.Accesso extension ServiceNetwork: @retroactive ContentIdentifiable {} extension ServiceNetwork: @retroactive ContentEquatable {} extension ServiceNetwork: Listable {} -extension PushNotificationAPI.Service: @retroactive ContentIdentifiable {} -extension PushNotificationAPI.Service: @retroactive ContentEquatable {} -extension PushNotificationAPI.Service: Listable {} +extension Network.PushNotification.Service: @retroactive ContentIdentifiable {} +extension Network.PushNotification.Service: @retroactive ContentEquatable {} +extension Network.PushNotification.Service: Listable {} extension Log.Level: @retroactive ContentIdentifiable {} extension Log.Level: @retroactive ContentEquatable {} extension Log.Level: Listable {} diff --git a/Session/Settings/HelpViewModel.swift b/Session/Settings/HelpViewModel.swift index ba9d899f8c..4ab69ce0d1 100644 --- a/Session/Settings/HelpViewModel.swift +++ b/Session/Settings/HelpViewModel.swift @@ -76,7 +76,8 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa trailingAccessory: .icon( UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), - size: .small + size: .small, + pinEdges: [.right] ), onTap: { guard let url: URL = URL(string: "https://getsession.org/translate") else { @@ -97,7 +98,8 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa trailingAccessory: .icon( UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), - size: .small + size: .small, + pinEdges: [.right] ), onTap: { guard let url: URL = URL(string: "https://getsession.org/survey") else { @@ -118,7 +120,8 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa trailingAccessory: .icon( UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), - size: .small + size: .small, + pinEdges: [.right] ), onTap: { guard let url: URL = URL(string: "https://getsession.org/faq") else { @@ -139,7 +142,8 @@ class HelpViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTa trailingAccessory: .icon( UIImage(systemName: "arrow.up.forward.app")? .withRenderingMode(.alwaysTemplate), - size: .small + size: .small, + pinEdges: [.right] ), onTap: { guard let url: URL = URL(string: "https://sessionapp.zendesk.com/hc/en-us") else { diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index 4ade2b621d..ffc90bcd3d 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -4,7 +4,7 @@ import UIKit import Combine import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SignalUtilitiesKit import SessionUtilitiesKit @@ -201,7 +201,7 @@ final class NukeDataModal: Modal { try communityAuth.compactMap { authMethod in switch authMethod.info { case .community(let server, _, _, _, _): - return try OpenGroupAPI.preparedClearInbox( + return try Network.SOGS.preparedClearInbox( requestAndPathBuildTimeout: Network.defaultTimeout, authMethod: authMethod, using: dependencies @@ -218,7 +218,7 @@ final class NukeDataModal: Modal { .eraseToAnyPublisher() } .tryFlatMap { authMethod, clearedServers in - try SnodeAPI + try Network.SnodeAPI .preparedDeleteAllMessages( namespace: .all, requestAndPathBuildTimeout: Network.defaultTimeout, @@ -296,7 +296,7 @@ final class NukeDataModal: Modal { UIApplication.shared.unregisterForRemoteNotifications() if let deviceToken: String = maybeDeviceToken, dependencies[singleton: .storage].isValid { - PushNotificationAPI + Network.PushNotification .unsubscribeAll(token: Data(hex: deviceToken), using: dependencies) .sinkUntilComplete() } diff --git a/Session/Settings/PrivacySettingsViewModel.swift b/Session/Settings/PrivacySettingsViewModel.swift index 86f4dc37b7..70fcf82ea1 100644 --- a/Session/Settings/PrivacySettingsViewModel.swift +++ b/Session/Settings/PrivacySettingsViewModel.swift @@ -12,7 +12,6 @@ import SessionUtilitiesKit class PrivacySettingsViewModel: SessionTableViewModel, NavigationItemSource, NavigatableStateHolder, ObservableTableSource { public let dependencies: Dependencies public let navigatableState: NavigatableState = NavigatableState() - public let editableState: EditableState = EditableState() public let state: TableDataState = TableDataState() public let observableState: ObservableTableSourceState = ObservableTableSourceState() private let shouldShowCloseButton: Bool @@ -233,7 +232,9 @@ class PrivacySettingsViewModel: SessionTableViewModel, NavigationItemSource, Nav ), confirmationInfo: ConfirmationModal.Info( title: "callsVoiceAndVideoBeta".localized(), - body: .text("callsVoiceAndVideoModalDescription".localized()), + body: .text("callsVoiceAndVideoModalDescription" + .put(key: "session_foundation", value: Constants.session_foundation) + .localized()), showCondition: .disabled, confirmTitle: "theContinue".localized(), confirmStyle: .danger, diff --git a/Session/Settings/RecoveryPasswordScreen.swift b/Session/Settings/RecoveryPasswordScreen.swift index 9afd6dcd93..1fd3034852 100644 --- a/Session/Settings/RecoveryPasswordScreen.swift +++ b/Session/Settings/RecoveryPasswordScreen.swift @@ -66,11 +66,15 @@ struct RecoveryPasswordScreen: View { } .padding(.bottom, Values.smallSpacing) - Text("recoveryPasswordDescription".localized()) - .font(.system(size: Values.smallFontSize)) - .foregroundColor(themeColor: .textPrimary) - .padding(.bottom, Values.mediumSpacing) - .fixedSize(horizontal: false, vertical: true) + AttributedText( + "recoveryPasswordDescription".localizedFormatted( + baseFont: .systemFont(ofSize: Values.smallFontSize) + ) + ) + .font(.system(size: Values.smallFontSize)) + .foregroundColor(themeColor: .textPrimary) + .padding(.bottom, Values.mediumSpacing) + .fixedSize(horizontal: false, vertical: true) if self.showQRCode { QRCodeView( diff --git a/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift b/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift index 2446f852f2..dfefd70e69 100644 --- a/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift +++ b/Session/Settings/SessionNetworkScreen/SessionNetworkScreen+ViewModel.swift @@ -5,7 +5,7 @@ import SwiftUI import Combine import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import SessionMessagingKit @@ -78,8 +78,8 @@ extension SessionNetworkScreenContent { self.isRefreshing.toggle() self.lastRefreshWasSuccessful = false - SessionNetworkAPI.client.getInfo(using: dependencies) - .subscribe(on: SessionNetworkAPI.workQueue, using: dependencies) + Network.SessionNetwork.client.getInfo(using: dependencies) + .subscribe(on: Network.SessionNetwork.workQueue, using: dependencies) .receive(on: DispatchQueue.main) .sink( receiveCompletion: { _ in }, diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index 2220e6279f..f8fa6a6a42 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -285,8 +285,7 @@ class SettingsViewModel: SessionTableViewModel, NavigationItemSource, Navigatabl title: SessionCell.TextInfo( state.profile.displayName(), font: .titleLarge, - alignment: .center, - interaction: .editable + alignment: .center ), trailingAccessory: .icon( .pencil, diff --git a/Session/Settings/Views/NewTagView.swift b/Session/Settings/Views/NewTagView.swift index f50daca9d8..416f0ae49b 100644 --- a/Session/Settings/Views/NewTagView.swift +++ b/Session/Settings/Views/NewTagView.swift @@ -34,7 +34,7 @@ final class NewTagView: UIView { private func setupUI() { addSubview(newTagLabel) - newTagLabel.pin(.leading, to: .leading, of: self, withInset: -Values.mediumSpacing + Values.verySmallSpacing) + newTagLabel.pin(.leading, to: .leading, of: self, withInset: -Values.mediumSmallSpacing) newTagLabel.pin([ UIView.VerticalEdge.top, UIView.VerticalEdge.bottom, UIView.HorizontalEdge.trailing ], to: self) } diff --git a/Session/Shared/SessionTableViewController.swift b/Session/Shared/SessionTableViewController.swift index 439421c5e3..8e0119bd9a 100644 --- a/Session/Shared/SessionTableViewController.swift +++ b/Session/Shared/SessionTableViewController.swift @@ -362,34 +362,6 @@ class SessionTableViewController: BaseVC, UITableViewDataSource, UITa disposables: &disposables ) - (viewModel as? ErasedEditableStateHolder)?.isEditing - .receive(on: DispatchQueue.main) - .sink { [weak self, weak tableView] isEditing in - UIView.animate(withDuration: 0.25) { - self?.setEditing(isEditing, animated: true) - - tableView?.visibleCells - .compactMap { $0 as? SessionCell } - .filter { $0.interactionMode == .editable || $0.interactionMode == .alwaysEditing } - .enumerated() - .forEach { index, cell in - cell.update( - isEditing: (isEditing || cell.interactionMode == .alwaysEditing), - becomeFirstResponder: ( - isEditing && - index == 0 && - cell.interactionMode != .alwaysEditing - ), - animated: true - ) - } - - tableView?.beginUpdates() - tableView?.endUpdates() - } - } - .store(in: &disposables) - viewModel.bannerInfo .receive(on: DispatchQueue.main) .sink { [weak self] info in @@ -489,21 +461,6 @@ class SessionTableViewController: BaseVC, UITableViewDataSource, UITa }, using: viewModel.dependencies ) - cell.update( - isEditing: (self.isEditing || (info.title?.interaction == .alwaysEditing)), - becomeFirstResponder: false, - animated: false - ) - - switch viewModel { - case let editableStateHolder as ErasedEditableStateHolder: - cell.textPublisher - .sink(receiveValue: { [weak editableStateHolder] text in - editableStateHolder?.textChanged(text, for: info.id) - }) - .store(in: &cell.disposables) - default: break - } case (let cell as FullConversationCell, let threadInfo as SessionCell.Info): cell.accessibilityIdentifier = info.accessibility?.identifier diff --git a/Session/Shared/Types/EditableState.swift b/Session/Shared/Types/EditableState.swift deleted file mode 100644 index 873e9da2b5..0000000000 --- a/Session/Shared/Types/EditableState.swift +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import Combine -import DifferenceKit -import SessionUtilitiesKit - -// MARK: - EditableStateHolder - -public protocol EditableStateHolder: AnyObject, TableData, ErasedEditableStateHolder { - var editableState: EditableState { get } -} - -public extension EditableStateHolder { - var textChanged: AnyPublisher<(text: String?, item: TableItem), Never> { editableState.textChanged } - - func setIsEditing(_ isEditing: Bool) { - editableState._isEditing.send(isEditing) - } - - func textChanged(_ text: String?, for item: TableItem) { - editableState._textChanged.send((text, item)) - } -} - -// MARK: - ErasedEditableStateHolder - -public protocol ErasedEditableStateHolder: AnyObject { - var isEditing: AnyPublisher { get } - - func setIsEditing(_ isEditing: Bool) - func textChanged(_ text: String?, for item: Item) -} - -public extension ErasedEditableStateHolder { - var isEditing: AnyPublisher { Just(false).eraseToAnyPublisher() } - - func setIsEditing(_ isEditing: Bool) {} - func textChanged(_ text: String?, for item: Item) {} -} - -public extension ErasedEditableStateHolder where Self: EditableStateHolder { - var isEditing: AnyPublisher { editableState.isEditing } - - func setIsEditing(_ isEditing: Bool) { - editableState._isEditing.send(isEditing) - } - - func textChanged(_ text: String?, for item: Item) { - guard let convertedItem: TableItem = item as? TableItem else { return } - - editableState._textChanged.send((text, convertedItem)) - } -} - -// MARK: - EditableState - -public struct EditableState { - let isEditing: AnyPublisher - let textChanged: AnyPublisher<(text: String?, item: TableItem), Never> - - // MARK: - Internal Variables - - fileprivate let _isEditing: CurrentValueSubject = CurrentValueSubject(false) - fileprivate let _textChanged: PassthroughSubject<(text: String?, item: TableItem), Never> = PassthroughSubject() - - // MARK: - Initialization - - init() { - self.isEditing = _isEditing - .removeDuplicates() - .shareReplay(1) - self.textChanged = _textChanged - .eraseToAnyPublisher() - } -} diff --git a/Session/Shared/Types/SessionCell+Accessory.swift b/Session/Shared/Types/SessionCell+Accessory.swift index ed8d6b0a97..887a554092 100644 --- a/Session/Shared/Types/SessionCell+Accessory.swift +++ b/Session/Shared/Types/SessionCell+Accessory.swift @@ -40,6 +40,7 @@ public extension SessionCell.Accessory { size: IconSize = .medium, customTint: ThemeValue? = nil, shouldFill: Bool = false, + pinEdges: [UIView.HorizontalEdge] = [.leading, .trailing], accessibility: Accessibility? = nil ) -> SessionCell.Accessory { return SessionCell.AccessoryConfig.Icon( @@ -48,6 +49,7 @@ public extension SessionCell.Accessory { iconSize: size, customTint: customTint, shouldFill: shouldFill, + pinEdges: pinEdges, accessibility: accessibility ) } @@ -57,6 +59,7 @@ public extension SessionCell.Accessory { size: IconSize = .medium, customTint: ThemeValue? = nil, shouldFill: Bool = false, + pinEdges: [UIView.HorizontalEdge] = [.leading, .trailing], accessibility: Accessibility? = nil ) -> SessionCell.Accessory { return SessionCell.AccessoryConfig.Icon( @@ -65,6 +68,7 @@ public extension SessionCell.Accessory { iconSize: size, customTint: customTint, shouldFill: shouldFill, + pinEdges: pinEdges, accessibility: accessibility ) } @@ -74,6 +78,7 @@ public extension SessionCell.Accessory { source: ImageDataManager.DataSource?, customTint: ThemeValue? = nil, shouldFill: Bool = false, + pinEdges: [UIView.HorizontalEdge] = [.leading, .trailing], accessibility: Accessibility? = nil ) -> SessionCell.Accessory { return SessionCell.AccessoryConfig.IconAsync( @@ -81,6 +86,7 @@ public extension SessionCell.Accessory { source: source, customTint: customTint, shouldFill: shouldFill, + pinEdges: pinEdges, accessibility: accessibility ) } @@ -226,6 +232,7 @@ public extension SessionCell.AccessoryConfig { public let iconSize: IconSize public let customTint: ThemeValue? public let shouldFill: Bool + public let pinEdges: [UIView.HorizontalEdge] fileprivate init( icon: Lucide.Icon?, @@ -233,6 +240,7 @@ public extension SessionCell.AccessoryConfig { iconSize: IconSize, customTint: ThemeValue?, shouldFill: Bool, + pinEdges: [UIView.HorizontalEdge], accessibility: Accessibility? ) { self.icon = icon @@ -240,6 +248,7 @@ public extension SessionCell.AccessoryConfig { self.iconSize = iconSize self.customTint = customTint self.shouldFill = shouldFill + self.pinEdges = pinEdges super.init(accessibility: accessibility) } @@ -252,6 +261,7 @@ public extension SessionCell.AccessoryConfig { iconSize.hash(into: &hasher) customTint.hash(into: &hasher) shouldFill.hash(into: &hasher) + pinEdges.hash(into: &hasher) accessibility.hash(into: &hasher) } @@ -264,7 +274,9 @@ public extension SessionCell.AccessoryConfig { iconSize == rhs.iconSize && customTint == rhs.customTint && shouldFill == rhs.shouldFill && + pinEdges == rhs.pinEdges && accessibility == rhs.accessibility + ) } } @@ -278,18 +290,21 @@ public extension SessionCell.AccessoryConfig { public let source: ImageDataManager.DataSource? public let customTint: ThemeValue? public let shouldFill: Bool + public let pinEdges: [UIView.HorizontalEdge] fileprivate init( iconSize: IconSize, source: ImageDataManager.DataSource?, customTint: ThemeValue?, shouldFill: Bool, + pinEdges: [UIView.HorizontalEdge], accessibility: Accessibility? ) { self.iconSize = iconSize self.source = source self.customTint = customTint self.shouldFill = shouldFill + self.pinEdges = pinEdges super.init(accessibility: accessibility) } @@ -301,6 +316,7 @@ public extension SessionCell.AccessoryConfig { source?.hash(into: &hasher) customTint.hash(into: &hasher) shouldFill.hash(into: &hasher) + pinEdges.hash(into: &hasher) accessibility.hash(into: &hasher) } @@ -312,6 +328,7 @@ public extension SessionCell.AccessoryConfig { source == rhs.source && customTint == rhs.customTint && shouldFill == rhs.shouldFill && + pinEdges == rhs.pinEdges && accessibility == rhs.accessibility ) } diff --git a/Session/Shared/Types/SessionCell+Styling.swift b/Session/Shared/Types/SessionCell+Styling.swift index a83a09a0da..8df10aa317 100644 --- a/Session/Shared/Types/SessionCell+Styling.swift +++ b/Session/Shared/Types/SessionCell+Styling.swift @@ -9,9 +9,7 @@ public extension SessionCell { struct TextInfo: Hashable, Equatable { public enum Interaction: Hashable, Equatable { case none - case editable case copy - case alwaysEditing case expandable } diff --git a/Session/Shared/Views/SessionCell+AccessoryView.swift b/Session/Shared/Views/SessionCell+AccessoryView.swift index 8d7c9ee551..ef3e66cf9e 100644 --- a/Session/Shared/Views/SessionCell+AccessoryView.swift +++ b/Session/Shared/Views/SessionCell+AccessoryView.swift @@ -55,7 +55,6 @@ extension SessionCell { accessory: accessory, tintColor: tintColor, isEnabled: isEnabled, - maxContentWidth: maxContentWidth, using: dependencies ) return @@ -73,7 +72,6 @@ extension SessionCell { if let newView: UIView = maybeView { addSubview(newView) - newView.pin(to: self) layout(view: newView, accessory: accessory) } @@ -82,7 +80,6 @@ extension SessionCell { accessory: accessory, tintColor: tintColor, isEnabled: isEnabled, - maxContentWidth: maxContentWidth, using: dependencies ) @@ -163,14 +160,16 @@ extension SessionCell { return createIconView(using: dependencies) case is SessionCell.AccessoryConfig.Toggle: return createToggleView() - case is SessionCell.AccessoryConfig.DropDown: return createDropDownView() + case is SessionCell.AccessoryConfig.DropDown: + return createDropDownView(maxContentWidth: maxContentWidth) + case is SessionCell.AccessoryConfig.Radio: return createRadioView() case is SessionCell.AccessoryConfig.HighlightingBackgroundLabel: - return createHighlightingBackgroundLabelView() + return createHighlightingBackgroundLabelView(maxContentWidth: maxContentWidth) case is SessionCell.AccessoryConfig.HighlightingBackgroundLabelAndRadio: - return createHighlightingBackgroundLabelAndRadioView() + return createHighlightingBackgroundLabelAndRadioView(maxContentWidth: maxContentWidth) case is SessionCell.AccessoryConfig.DisplayPicture: return createDisplayPictureView() case is SessionCell.AccessoryConfig.Search: return createSearchView() @@ -188,14 +187,24 @@ extension SessionCell { return nil } } - + private func layout(view: UIView?, accessory: Accessory) { switch accessory { case let accessory as SessionCell.AccessoryConfig.Icon: - layoutIconView(view, iconSize: accessory.iconSize, shouldFill: accessory.shouldFill) + layoutIconView( + view, + iconSize: accessory.iconSize, + shouldFill: accessory.shouldFill, + pin: accessory.pinEdges + ) case let accessory as SessionCell.AccessoryConfig.IconAsync: - layoutIconView(view, iconSize: accessory.iconSize, shouldFill: accessory.shouldFill) + layoutIconView( + view, + iconSize: accessory.iconSize, + shouldFill: accessory.shouldFill, + pin: accessory.pinEdges + ) case is SessionCell.AccessoryConfig.Toggle: layoutToggleView(view) case is SessionCell.AccessoryConfig.DropDown: layoutDropDownView(view) @@ -227,7 +236,6 @@ extension SessionCell { accessory: Accessory, tintColor: ThemeValue, isEnabled: Bool, - maxContentWidth: CGFloat, using dependencies: Dependencies ) { switch accessory { @@ -288,13 +296,25 @@ extension SessionCell { return result } - private func layoutIconView(_ view: UIView?, iconSize: IconSize, shouldFill: Bool) { + private func layoutIconView(_ view: UIView?, iconSize: IconSize, shouldFill: Bool, pin edges: [UIView.HorizontalEdge]) { guard let imageView: SessionImageView = view as? SessionImageView else { return } imageView.set(.width, to: iconSize.size) imageView.set(.height, to: iconSize.size) - imageView.pin(.leading, to: .leading, of: self, withInset: (shouldFill ? 0 : Values.smallSpacing)) - imageView.pin(.trailing, to: .trailing, of: self, withInset: (shouldFill ? 0 : -Values.smallSpacing)) + imageView.pin(.top, to: .top, of: self) + imageView.pin(.bottom, to: .bottom, of: self) + + let shouldInvertPadding: [UIView.HorizontalEdge] = [.left, .trailing] + + for edge in edges { + let inset: CGFloat = ( + (shouldFill ? 0 : Values.smallSpacing) * + (shouldInvertPadding.contains(edge) ? -1 : 1) + ) + + imageView.pin(edge, to: edge, of: self, withInset: inset) + } + fixedWidthConstraint.isActive = (iconSize.size <= fixedWidthConstraint.constant) minWidthConstraint.isActive = !fixedWidthConstraint.isActive } @@ -306,7 +326,7 @@ extension SessionCell { imageView.accessibilityLabel = accessory.accessibility?.label imageView.themeTintColor = (accessory.customTint ?? tintColor) imageView.contentMode = (accessory.shouldFill ? .scaleAspectFill : .scaleAspectFit) - + switch (accessory.icon, accessory.image) { case (.some(let icon), _): imageView.image = Lucide @@ -377,7 +397,7 @@ extension SessionCell { // MARK: -- DropDown - private func createDropDownView() -> UIView { + private func createDropDownView(maxContentWidth: CGFloat) -> UIView { let result: UIStackView = UIStackView() result.translatesAutoresizingMaskIntoConstraints = false result.axis = .horizontal @@ -397,6 +417,7 @@ extension SessionCell { label.themeTextColor = .textPrimary label.setContentHugging(to: .required) label.setCompressionResistance(to: .required) + label.preferredMaxLayoutWidth = (maxContentWidth * 0.4) /// Limit to 40% of content width label.numberOfLines = 0 result.addArrangedSubview(imageView) @@ -511,8 +532,11 @@ extension SessionCell { // MARK: -- HighlightingBackgroundLabel - private func createHighlightingBackgroundLabelView() -> UIView { - return SessionHighlightingBackgroundLabel() + private func createHighlightingBackgroundLabelView(maxContentWidth: CGFloat) -> UIView { + let result: SessionHighlightingBackgroundLabel = SessionHighlightingBackgroundLabel() + result.preferredMaxLayoutWidth = (maxContentWidth * 0.4) /// Limit to 40% of content width + + return result } private func layoutHighlightingBackgroundLabelView(_ view: UIView?) { @@ -541,10 +565,11 @@ extension SessionCell { // MARK: -- HighlightingBackgroundLabelAndRadio - private func createHighlightingBackgroundLabelAndRadioView() -> UIView { + private func createHighlightingBackgroundLabelAndRadioView(maxContentWidth: CGFloat) -> UIView { let result: UIView = UIView() let label: SessionHighlightingBackgroundLabel = SessionHighlightingBackgroundLabel() let radio: UIView = createRadioView() + label.preferredMaxLayoutWidth = (maxContentWidth * 0.4) /// Limit to 40% of content width result.addSubview(label) result.addSubview(radio) @@ -560,6 +585,8 @@ extension SessionCell { let radioView: UIView = radioBorderView.subviews.first else { return } + label.pin(to: self) + label.pin(.top, to: .top, of: self) label.pin(.leading, to: .leading, of: self, withInset: Values.smallSpacing) label.pin(.trailing, to: .leading, of: radioBorderView, withInset: -Values.smallSpacing) @@ -755,8 +782,7 @@ extension SessionCell { minWidthConstraint.isActive = true } - view.pin(.leading, to: .leading, of: self, withInset: Values.smallSpacing) - view.pin(.trailing, to: .trailing, of: self, withInset: -Values.smallSpacing) + view.pin(to: self) } private func configureCustomView(_ view: UIView?, _ accessory: SessionCell.AccessoryConfig.AnyCustom) { diff --git a/Session/Shared/Views/SessionCell.swift b/Session/Shared/Views/SessionCell.swift index 47bdebb394..9025578221 100644 --- a/Session/Shared/Views/SessionCell.swift +++ b/Session/Shared/Views/SessionCell.swift @@ -10,7 +10,6 @@ import SessionUtilitiesKit public class SessionCell: UITableViewCell { public static let cornerRadius: CGFloat = 17 - private var isEditingTitle = false public private(set) var interactionMode: SessionCell.TextInfo.Interaction = .none public var lastTouchLocation: UITouch? private var shouldHighlightTitle: Bool = true @@ -34,10 +33,6 @@ public class SessionCell: UITableViewCell { private lazy var contentStackViewHorizontalCenterConstraint: NSLayoutConstraint = contentStackView.center(.horizontal, in: cellBackgroundView) private lazy var contentStackViewWidthConstraint: NSLayoutConstraint = contentStackView.set(.width, lessThanOrEqualTo: .width, of: cellBackgroundView) private lazy var leadingAccessoryFillConstraint: NSLayoutConstraint = contentStackView.set(.height, to: .height, of: leadingAccessoryView) - private lazy var titleTextFieldLeadingConstraint: NSLayoutConstraint = titleTextField.pin(.leading, to: .leading, of: cellBackgroundView) - private lazy var titleTextFieldTrailingConstraint: NSLayoutConstraint = titleTextField.pin(.trailing, to: .trailing, of: cellBackgroundView) - private lazy var titleMinHeightConstraint: NSLayoutConstraint = titleStackView.heightAnchor - .constraint(greaterThanOrEqualTo: titleTextField.heightAnchor) private lazy var trailingAccessoryFillConstraint: NSLayoutConstraint = contentStackView.set(.height, to: .height, of: trailingAccessoryView) private lazy var accessoryWidthMatchConstraint: NSLayoutConstraint = leadingAccessoryView.set(.width, to: .width, of: trailingAccessoryView) @@ -109,17 +104,6 @@ public class SessionCell: UITableViewCell { return result }() - fileprivate let titleTextField: UITextField = { - let textField: SNTextField = SNTextField(placeholder: "", usesDefaultHeight: false) - textField.translatesAutoresizingMaskIntoConstraints = false - textField.textAlignment = .center - textField.alpha = 0 - textField.isHidden = true - textField.set(.height, to: Values.largeButtonHeight) - - return textField - }() - private let subtitleLabel: SRCopyableLabel = { let result: SRCopyableLabel = SRCopyableLabel() result.translatesAutoresizingMaskIntoConstraints = false @@ -195,8 +179,6 @@ public class SessionCell: UITableViewCell { titleStackView.addArrangedSubview(titleLabel) titleStackView.addArrangedSubview(subtitleLabel) - cellBackgroundView.addSubview(titleTextField) - setupLayout() } @@ -215,8 +197,6 @@ public class SessionCell: UITableViewCell { contentStackViewTopConstraint.isActive = true contentStackViewBottomConstraint.isActive = true - titleTextField.center(.vertical, in: titleLabel) - botSeparatorLeadingConstraint = botSeparator.pin(.leading, to: .leading, of: cellBackgroundView) botSeparatorTrailingConstraint = botSeparator.pin(.trailing, to: .trailing, of: cellBackgroundView) botSeparator.pin(.bottom, to: .bottom, of: cellBackgroundView) @@ -297,7 +277,6 @@ public class SessionCell: UITableViewCell { public override func prepareForReuse() { super.prepareForReuse() - isEditingTitle = false interactionMode = .none shouldHighlightTitle = true accessibilityIdentifier = nil @@ -315,18 +294,12 @@ public class SessionCell: UITableViewCell { contentStackViewTrailingConstraint.isActive = false contentStackViewHorizontalCenterConstraint.isActive = false contentStackViewWidthConstraint.isActive = false - titleMinHeightConstraint.isActive = false leadingAccessoryView.prepareForReuse() leadingAccessoryView.alpha = 1 leadingAccessoryFillConstraint.isActive = false titleLabel.text = "" titleLabel.themeTextColor = .textPrimary titleLabel.alpha = 1 - titleTextField.text = "" - titleTextField.textAlignment = .center - titleTextField.themeTextColor = .textPrimary - titleTextField.isHidden = true - titleTextField.alpha = 0 subtitleLabel.isUserInteractionEnabled = false subtitleLabel.attributedText = nil subtitleLabel.themeTextColor = .textPrimary @@ -418,16 +391,6 @@ public class SessionCell: UITableViewCell { return -(leadingFitToEdge || trailingFitToEdge ? 0 : Values.mediumSpacing) }() - titleTextFieldLeadingConstraint.constant = { - guard info.styling.backgroundStyle != .noBackground else { return 0 } - - return (leadingFitToEdge ? 0 : Values.mediumSpacing) - }() - titleTextFieldTrailingConstraint.constant = { - guard info.styling.backgroundStyle != .noBackground else { return 0 } - - return -(trailingFitToEdge ? 0 : Values.mediumSpacing) - }() // Styling and positioning let defaultEdgePadding: CGFloat @@ -567,27 +530,21 @@ public class SessionCell: UITableViewCell { titleLabel.accessibilityIdentifier = info.title?.accessibility?.identifier titleLabel.accessibilityLabel = info.title?.accessibility?.label titleLabel.isHidden = (info.title == nil) - titleTextField.text = info.title?.text - titleTextField.textAlignment = (info.title?.textAlignment ?? .left) - titleTextField.placeholder = info.title?.editingPlaceholder - titleTextField.isHidden = (info.title == nil) - titleTextField.accessibilityIdentifier = info.title?.accessibility?.identifier - titleTextField.accessibilityLabel = info.title?.accessibility?.label subtitleLabel.isUserInteractionEnabled = (info.subtitle?.interaction == .copy) subtitleLabel.font = info.subtitle?.font + subtitleLabel.themeTextColor = info.styling.subtitleTintColor subtitleLabel.themeAttributedText = info.subtitle.map { subtitle -> ThemedAttributedString? in ThemedAttributedString(stringWithHTMLTags: subtitle.text, font: subtitle.font) } - subtitleLabel.themeTextColor = info.styling.subtitleTintColor subtitleLabel.textAlignment = (info.subtitle?.textAlignment ?? .left) subtitleLabel.accessibilityIdentifier = info.subtitle?.accessibility?.identifier subtitleLabel.accessibilityLabel = info.subtitle?.accessibility?.label subtitleLabel.isHidden = (info.subtitle == nil) expandableDescriptionLabel.font = info.description?.font ?? .systemFont(ofSize: 12) + expandableDescriptionLabel.themeTextColor = info.styling.descriptionTintColor expandableDescriptionLabel.themeAttributedText = info.description.map { description -> ThemedAttributedString? in ThemedAttributedString(stringWithHTMLTags: description.text, font: description.font) } - expandableDescriptionLabel.themeTextColor = info.styling.descriptionTintColor expandableDescriptionLabel.textAlignment = (info.description?.textAlignment ?? .left) expandableDescriptionLabel.accessibilityIdentifier = info.description?.accessibility?.identifier expandableDescriptionLabel.accessibilityLabel = info.description?.accessibility?.label @@ -602,57 +559,17 @@ public class SessionCell: UITableViewCell { ) } - public func update(isEditing: Bool, becomeFirstResponder: Bool, animated: Bool) { - // Note: We set 'isUserInteractionEnabled' based on the 'info.isEditable' flag - // so can use that to determine whether this element can become editable - guard interactionMode == .editable || interactionMode == .alwaysEditing else { return } - - self.isEditingTitle = isEditing - - let changes = { [weak self] in - self?.titleLabel.alpha = (isEditing ? 0 : 1) - self?.titleTextField.alpha = (isEditing ? 1 : 0) - self?.leadingAccessoryView.alpha = (isEditing ? 0 : 1) - self?.trailingAccessoryView.alpha = (isEditing ? 0 : 1) - self?.titleMinHeightConstraint.isActive = isEditing - } - let completion: (Bool) -> Void = { [weak self] complete in - self?.titleTextField.text = self?.originalInputValue - } - - if animated { - UIView.animate(withDuration: 0.25, animations: changes, completion: completion) - } - else { - changes() - completion(true) - } - - if isEditing && becomeFirstResponder { - titleTextField.becomeFirstResponder() - } - else if !isEditing { - titleTextField.resignFirstResponder() - } - } - // MARK: - Interaction public override func setHighlighted(_ highlighted: Bool, animated: Bool) { super.setHighlighted(highlighted, animated: animated) - // When editing disable the highlighted state changes (would result in UI elements - // reappearing otherwise) - guard !self.isEditingTitle else { return } - // If the 'cellSelectedBackgroundView' is hidden then there is no background so we // should update the titleLabel to indicate the highlighted state if cellSelectedBackgroundView.isHidden && shouldHighlightTitle { // Note: We delay the "unhighlight" of the titleLabel so that the transition doesn't // conflict with the transition into edit mode DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) { [weak self] in - guard self?.isEditingTitle == false else { return } - self?.titleLabel.alpha = (highlighted ? 0.8 : 1) } } @@ -675,22 +592,3 @@ public class SessionCell: UITableViewCell { lastTouchLocation = touches.first } } - -// MARK: - Compose - -extension CombineCompatible where Self: SessionCell { - var textPublisher: AnyPublisher { - return self.titleTextField.publisher(for: [.editingChanged, .editingDidEnd]) - .handleEvents( - receiveOutput: { [weak self] textField in - // When editing the text update the 'accessibilityLabel' of the cell to match - // the text - let targetText: String? = (textField.isEditing ? textField.text : self?.titleLabel.text) - self?.accessibilityLabel = (targetText ?? self?.accessibilityLabel) - } - ) - .filter { $0.isEditing } // Don't bother sending events for 'editingDidEnd' - .map { textField -> String in (textField.text ?? "") } - .eraseToAnyPublisher() - } -} diff --git a/Session/Shared/Views/SessionHeaderView.swift b/Session/Shared/Views/SessionHeaderView.swift index ebe88b1b95..b845d027bb 100644 --- a/Session/Shared/Views/SessionHeaderView.swift +++ b/Session/Shared/Views/SessionHeaderView.swift @@ -6,14 +6,10 @@ import SessionUIKit class SessionHeaderView: UITableViewHeaderFooterView { // MARK: - UI - private lazy var titleLabelConstraints: [NSLayoutConstraint] = [ - titleLabel.pin(.top, to: .top, of: self, withInset: Values.mediumSpacing), - titleLabel.pin(.bottom, to: .bottom, of: self, withInset: -Values.mediumSpacing) - ] - private lazy var titleLabelLeadingConstraint: NSLayoutConstraint = titleLabel.pin(.leading, to: .leading, of: self) - private lazy var titleLabelTrailingConstraint: NSLayoutConstraint = titleLabel.pin(.trailing, to: .trailing, of: self) - private lazy var titleSeparatorLeadingConstraint: NSLayoutConstraint = titleSeparator.pin(.leading, to: .leading, of: self) - private lazy var titleSeparatorTrailingConstraint: NSLayoutConstraint = titleSeparator.pin(.trailing, to: .trailing, of: self) + private var titleLabelLeadingConstraint: NSLayoutConstraint? + private var titleLabelTrailingConstraint: NSLayoutConstraint? + private var titleSeparatorLeadingConstraint: NSLayoutConstraint? + private var titleSeparatorTrailingConstraint: NSLayoutConstraint? private let titleLabel: UILabel = { let result: UILabel = UILabel() @@ -51,9 +47,9 @@ class SessionHeaderView: UITableViewHeaderFooterView { self.backgroundView = UIView() self.backgroundView?.themeBackgroundColor = .backgroundPrimary - addSubview(titleLabel) - addSubview(titleSeparator) - addSubview(loadingIndicator) + contentView.addSubview(titleLabel) + contentView.addSubview(titleSeparator) + contentView.addSubview(loadingIndicator) setupLayout() } @@ -63,12 +59,18 @@ class SessionHeaderView: UITableViewHeaderFooterView { } private func setupLayout() { - titleLabel.pin(.top, to: .top, of: self, withInset: Values.mediumSpacing) - titleLabel.pin(.bottom, to: .bottom, of: self, withInset: Values.mediumSpacing) - titleLabel.center(.vertical, in: self) + titleLabel.pin(.top, to: .top, of: contentView, withInset: Values.mediumSpacing) + titleLabelLeadingConstraint = titleLabel.pin(.leading, to: .leading, of: contentView) + titleLabelTrailingConstraint = titleLabel.pin(.trailing, to: .trailing, of: contentView) + titleLabel + .pin(.bottom, to: .bottom, of: contentView, withInset: -Values.mediumSpacing) + .setting(priority: .defaultHigh) - titleSeparator.center(.vertical, in: self) - loadingIndicator.center(in: self) + titleSeparator.center(.vertical, in: contentView) + titleSeparatorLeadingConstraint = titleSeparator.pin(.leading, to: .leading, of: contentView) + titleSeparatorTrailingConstraint = titleSeparator.pin(.trailing, to: .trailing, of: contentView) + + loadingIndicator.center(in: contentView) } // MARK: - Content @@ -79,14 +81,6 @@ class SessionHeaderView: UITableViewHeaderFooterView { titleLabel.isHidden = true titleSeparator.isHidden = true loadingIndicator.isHidden = true - - titleLabelLeadingConstraint.isActive = false - titleLabelTrailingConstraint.isActive = false - titleLabelConstraints.forEach { $0.isActive = false } - - titleSeparator.center(.vertical, in: self) - titleSeparatorLeadingConstraint.isActive = false - titleSeparatorTrailingConstraint.isActive = false } public func update( @@ -94,24 +88,19 @@ class SessionHeaderView: UITableViewHeaderFooterView { style: SessionTableSectionStyle = .titleRoundedContent ) { let titleIsEmpty: Bool = (title ?? "").isEmpty + titleLabelLeadingConstraint?.constant = style.edgePadding + titleLabelTrailingConstraint?.constant = -style.edgePadding + titleSeparatorLeadingConstraint?.constant = style.edgePadding + titleSeparatorTrailingConstraint?.constant = -style.edgePadding switch style { case .titleRoundedContent, .titleEdgeToEdgeContent, .titleNoBackgroundContent: titleLabel.text = title titleLabel.isHidden = titleIsEmpty - titleLabelLeadingConstraint.constant = style.edgePadding - titleLabelTrailingConstraint.constant = -style.edgePadding - titleLabelLeadingConstraint.isActive = !titleIsEmpty - titleLabelTrailingConstraint.isActive = !titleIsEmpty - titleLabelConstraints.forEach { $0.isActive = true } case .titleSeparator: titleSeparator.update(title: title) titleSeparator.isHidden = false - titleSeparatorLeadingConstraint.constant = style.edgePadding - titleSeparatorTrailingConstraint.constant = -style.edgePadding - titleSeparatorLeadingConstraint.isActive = !titleIsEmpty - titleSeparatorTrailingConstraint.isActive = !titleIsEmpty case .none, .padding: break case .loadMore: loadingIndicator.isHidden = false diff --git a/Session/Shared/Views/SessionHighlightingBackgroundLabel.swift b/Session/Shared/Views/SessionHighlightingBackgroundLabel.swift index 7e6d6ead22..6bbe3022cb 100644 --- a/Session/Shared/Views/SessionHighlightingBackgroundLabel.swift +++ b/Session/Shared/Views/SessionHighlightingBackgroundLabel.swift @@ -15,6 +15,11 @@ public class SessionHighlightingBackgroundLabel: UIView { set { label.themeTextColor = newValue } } + var preferredMaxLayoutWidth: CGFloat { + get { label.preferredMaxLayoutWidth - (Values.smallSpacing * 2) } + set { label.preferredMaxLayoutWidth = (newValue - (Values.smallSpacing * 2)) } + } + // MARK: - Components private let label: UILabel = { diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index bb30cb2599..b03942a719 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -5,7 +5,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit diff --git a/Session/Utilities/IP2Country.swift b/Session/Utilities/IP2Country.swift index 8ef98ce322..56b9102bba 100644 --- a/Session/Utilities/IP2Country.swift +++ b/Session/Utilities/IP2Country.swift @@ -5,7 +5,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Cache diff --git a/Session/Utilities/ImageLoading+Convenience.swift b/Session/Utilities/ImageLoading+Convenience.swift index 54a69d2524..1b1ef57fd3 100644 --- a/Session/Utilities/ImageLoading+Convenience.swift +++ b/Session/Utilities/ImageLoading+Convenience.swift @@ -69,10 +69,11 @@ public extension ImageDataManager.DataSource { // MARK: - ImageDataManagerType Convenience public extension ImageDataManagerType { + @MainActor func loadImage( attachment: Attachment, using dependencies: Dependencies, - onComplete: @escaping (ImageDataManager.ProcessedImageData?) -> Void = { _ in } + onComplete: @MainActor @escaping (ImageDataManager.ProcessedImageData?) -> Void = { _ in } ) { guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.from( attachment: attachment, @@ -82,11 +83,12 @@ public extension ImageDataManagerType { load(source, onComplete: onComplete) } + @MainActor func loadThumbnail( size: ImageDataManager.ThumbnailSize, attachment: Attachment, using dependencies: Dependencies, - onComplete: @escaping (ImageDataManager.ProcessedImageData?) -> Void = { _ in } + onComplete: @MainActor @escaping (ImageDataManager.ProcessedImageData?) -> Void = { _ in } ) { guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.thumbnailFrom( attachment: attachment, @@ -102,7 +104,7 @@ public extension ImageDataManagerType { public extension SessionImageView { @MainActor - func loadImage(from path: String, onComplete: ((ImageDataManager.ProcessedImageData?) -> Void)? = nil) { + func loadImage(from path: String, onComplete: (@MainActor (ImageDataManager.ProcessedImageData?) -> Void)? = nil) { loadImage(.url(URL(fileURLWithPath: path)), onComplete: onComplete) } @@ -110,7 +112,7 @@ public extension SessionImageView { func loadImage( attachment: Attachment, using dependencies: Dependencies, - onComplete: ((ImageDataManager.ProcessedImageData?) -> Void)? = nil + onComplete: (@MainActor (ImageDataManager.ProcessedImageData?) -> Void)? = nil ) { guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.from( attachment: attachment, @@ -128,7 +130,7 @@ public extension SessionImageView { size: ImageDataManager.ThumbnailSize, attachment: Attachment, using dependencies: Dependencies, - onComplete: ((ImageDataManager.ProcessedImageData?) -> Void)? = nil + onComplete: (@MainActor (ImageDataManager.ProcessedImageData?) -> Void)? = nil ) { guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.thumbnailFrom( attachment: attachment, @@ -143,7 +145,7 @@ public extension SessionImageView { } @MainActor - func loadPlaceholder(seed: String, text: String, size: CGFloat, onComplete: ((ImageDataManager.ProcessedImageData?) -> Void)? = nil) { + func loadPlaceholder(seed: String, text: String, size: CGFloat, onComplete: (@MainActor (ImageDataManager.ProcessedImageData?) -> Void)? = nil) { loadImage(.placeholderIcon(seed: seed, text: text, size: size), onComplete: onComplete) } } diff --git a/Session/Utilities/UIContextualAction+Utilities.swift b/Session/Utilities/UIContextualAction+Utilities.swift index 5b5ce426a4..c0edbc3b20 100644 --- a/Session/Utilities/UIContextualAction+Utilities.swift +++ b/Session/Utilities/UIContextualAction+Utilities.swift @@ -649,7 +649,7 @@ public extension UIContextualAction { guard !isMessageRequest else { switch threadViewModel.threadVariant { case .group: return ThemedAttributedString(string: "groupInviteDelete".localized()) - default: return ThemedAttributedString(string: "messageRequestsDelete".localized()) + default: return ThemedAttributedString(string: "messageRequestsContactDelete".localized()) } } @@ -692,7 +692,11 @@ public extension UIContextualAction { return .deleteGroupAndContent case (.group, _, _): return .leaveGroupAsync - case (.contact, _, _): return .deleteContactConversationAndMarkHidden + case (.contact, true, _): + return .deleteContactConversationAndContact + + case (.contact, false, _): + return .deleteContactConversationAndMarkHidden } }() diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 2f861719a4..5260915ddc 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -2,58 +2,53 @@ import Foundation import GRDB import SessionUtilitiesKit -public enum SNMessagingKit: MigratableTarget { // Just to make the external API nice - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .messagingKit, - migrations: [ - [ - _001_InitialSetupMigration.self, - _002_SetupStandardJobs.self - ], // Initial DB Creation - [ - _003_YDBToGRDBMigration.self - ], // YDB to GRDB Migration - [ - _004_RemoveLegacyYDB.self - ], // Legacy DB removal - [ - _005_FixDeletedMessageReadState.self, - _006_FixHiddenModAdminSupport.self, - _007_HomeQueryOptimisationIndexes.self - ], // Add job priorities - [ - _008_EmojiReacts.self, - _009_OpenGroupPermission.self, - _010_AddThreadIdToFTS.self - ], // Fix thread FTS - [ - _011_AddPendingReadReceipts.self, - _012_AddFTSIfNeeded.self, - _013_SessionUtilChanges.self, - _014_GenerateInitialUserConfigDumps.self, - _015_BlockCommunityMessageRequests.self, - _016_MakeBrokenProfileTimestampsNullable.self, - _017_RebuildFTSIfNeeded_2_4_5.self, - _018_DisappearingMessagesConfiguration.self, - _019_ScheduleAppUpdateCheckJob.self, - _020_AddMissingWhisperFlag.self, - _021_ReworkRecipientState.self, - _022_GroupsRebuildChanges.self, - _023_GroupsExpiredFlag.self, - _024_FixBustedInteractionVariant.self, - _025_DropLegacyClosedGroupKeyPairTable.self, - _026_MessageDeduplicationTable.self - ], - [], // Renamed `Setting` to `KeyValueStore` - [ - _027_MoveSettingsToLibSession.self, - _028_RenameAttachments.self, - _029_AddProMessageFlag.self - ] - ] - ) - } +public enum SNMessagingKit { // Just to make the external API nice + public static let migrations: [Migration.Type] = [ + _001_SUK_InitialSetupMigration.self, + _002_SUK_SetupStandardJobs.self, + _003_SUK_YDBToGRDBMigration.self, + _004_SNK_InitialSetupMigration.self, + _005_SNK_SetupStandardJobs.self, + _006_SMK_InitialSetupMigration.self, + _007_SMK_SetupStandardJobs.self, + _008_SNK_YDBToGRDBMigration.self, + _009_SMK_YDBToGRDBMigration.self, + _010_FlagMessageHashAsDeletedOrInvalid.self, + _011_RemoveLegacyYDB.self, + _012_AddJobPriority.self, + _013_FixDeletedMessageReadState.self, + _014_FixHiddenModAdminSupport.self, + _015_HomeQueryOptimisationIndexes.self, + _016_ThemePreferences.self, + _017_EmojiReacts.self, + _018_OpenGroupPermission.self, + _019_AddThreadIdToFTS.self, + _020_AddJobUniqueHash.self, + _021_AddSnodeReveivedMessageInfoPrimaryKey.self, + _022_DropSnodeCache.self, + _023_SplitSnodeReceivedMessageInfo.self, + _024_ResetUserConfigLastHashes.self, + _025_AddPendingReadReceipts.self, + _026_AddFTSIfNeeded.self, + _027_SessionUtilChanges.self, + _028_GenerateInitialUserConfigDumps.self, + _029_BlockCommunityMessageRequests.self, + _030_MakeBrokenProfileTimestampsNullable.self, + _031_RebuildFTSIfNeeded_2_4_5.self, + _032_DisappearingMessagesConfiguration.self, + _033_ScheduleAppUpdateCheckJob.self, + _034_AddMissingWhisperFlag.self, + _035_ReworkRecipientState.self, + _036_GroupsRebuildChanges.self, + _037_GroupsExpiredFlag.self, + _038_FixBustedInteractionVariant.self, + _039_DropLegacyClosedGroupKeyPairTable.self, + _040_MessageDeduplicationTable.self, + _041_RenameTableSettingToKeyValueStore.self, + _042_MoveSettingsToLibSession.self, + _043_RenameAttachments.self, + _044_AddProMessageFlag.self + ] public static func configure(using dependencies: Dependencies) { // Configure the job executors diff --git a/SessionMessagingKit/Crypto/Crypto+Attachments.swift b/SessionMessagingKit/Crypto/Crypto+Attachments.swift index 6caca2f459..ceb7cca191 100644 --- a/SessionMessagingKit/Crypto/Crypto+Attachments.swift +++ b/SessionMessagingKit/Crypto/Crypto+Attachments.swift @@ -4,7 +4,7 @@ import Foundation import CommonCrypto -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Encryption diff --git a/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift b/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift index bea6331051..ff92a72277 100644 --- a/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift +++ b/SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift @@ -4,7 +4,7 @@ import Foundation import CryptoKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtil import SessionUtilitiesKit @@ -164,38 +164,6 @@ public extension Crypto.Generator { } } - static func plaintextWithPushNotificationPayload( - payload: Data, - encKey: Data - ) -> Crypto.Generator { - return Crypto.Generator( - id: "plaintextWithPushNotificationPayload", - args: [payload, encKey] - ) { - var cPayload: [UInt8] = Array(payload) - var cEncKey: [UInt8] = Array(encKey) - var maybePlaintext: UnsafeMutablePointer? = nil - var plaintextLen: Int = 0 - - guard - cEncKey.count == 32, - session_decrypt_push_notification( - &cPayload, - cPayload.count, - &cEncKey, - &maybePlaintext, - &plaintextLen - ), - plaintextLen > 0, - let plaintext: Data = maybePlaintext.map({ Data(bytes: $0, count: plaintextLen) }) - else { throw MessageReceiverError.decryptionFailed } - - free(UnsafeMutableRawPointer(mutating: maybePlaintext)) - - return plaintext - } - } - static func plaintextWithMultiEncrypt( ciphertext: Data, senderSessionId: SessionId, @@ -231,7 +199,7 @@ public extension Crypto.Generator { static func messageServerHash( swarmPubkey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, data: Data ) -> Crypto.Generator { return Crypto.Generator( diff --git a/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionMessagingKit/Database/Migrations/_001_SUK_InitialSetupMigration.swift similarity index 94% rename from SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift rename to SessionMessagingKit/Database/Migrations/_001_SUK_InitialSetupMigration.swift index 038c6de166..d91ffaca7d 100644 --- a/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_001_SUK_InitialSetupMigration.swift @@ -4,10 +4,10 @@ import Foundation import GRDB +import SessionUtilitiesKit -enum _001_InitialSetupMigration: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "initialSetup" +enum _001_SUK_InitialSetupMigration: Migration { + static let identifier: String = "utilitiesKit.initialSetup" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [ Identity.self, Job.self, JobDependencies.self diff --git a/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionMessagingKit/Database/Migrations/_002_SUK_SetupStandardJobs.swift similarity index 89% rename from SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift rename to SessionMessagingKit/Database/Migrations/_002_SUK_SetupStandardJobs.swift index ca787c2c2c..8f27b313d5 100644 --- a/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionMessagingKit/Database/Migrations/_002_SUK_SetupStandardJobs.swift @@ -2,12 +2,12 @@ import Foundation import GRDB +import SessionUtilitiesKit /// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration /// before running the `YDBToGRDBMigration` -enum _002_SetupStandardJobs: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "SetupStandardJobs" +enum _002_SUK_SetupStandardJobs: Migration { + static let identifier: String = "utilitiesKit.SetupStandardJobs" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_003_SUK_YDBToGRDBMigration.swift similarity index 71% rename from SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift rename to SessionMessagingKit/Database/Migrations/_003_SUK_YDBToGRDBMigration.swift index 4e826bc308..5753532d9a 100644 --- a/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_003_SUK_YDBToGRDBMigration.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _003_YDBToGRDBMigration: Migration { - static let target: TargetMigrations.Identifier = .snodeKit - static let identifier: String = "YDBToGRDBMigration" +enum _003_SUK_YDBToGRDBMigration: Migration { + static let identifier: String = "utilitiesKit.YDBToGRDBMigration" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionMessagingKit/Database/Migrations/_004_SNK_InitialSetupMigration.swift similarity index 91% rename from SessionSnodeKit/Database/Migrations/_001_InitialSetupMigration.swift rename to SessionMessagingKit/Database/Migrations/_004_SNK_InitialSetupMigration.swift index 02f160fe0c..9852a0a11f 100644 --- a/SessionSnodeKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_004_SNK_InitialSetupMigration.swift @@ -6,9 +6,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _001_InitialSetupMigration: Migration { - static let target: TargetMigrations.Identifier = .snodeKit - static let identifier: String = "initialSetup" +enum _004_SNK_InitialSetupMigration: Migration { + static let identifier: String = "snodeKit.initialSetup" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionMessagingKit/Database/Migrations/_005_SNK_SetupStandardJobs.swift similarity index 91% rename from SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift rename to SessionMessagingKit/Database/Migrations/_005_SNK_SetupStandardJobs.swift index e92355cc9e..2241868cc8 100644 --- a/SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionMessagingKit/Database/Migrations/_005_SNK_SetupStandardJobs.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration /// before running the `YDBToGRDBMigration` -enum _002_SetupStandardJobs: Migration { - static let target: TargetMigrations.Identifier = .snodeKit - static let identifier: String = "SetupStandardJobs" +enum _005_SNK_SetupStandardJobs: Migration { + static let identifier: String = "snodeKit.SetupStandardJobs" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionMessagingKit/Database/Migrations/_006_SMK_InitialSetupMigration.swift similarity index 97% rename from SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift rename to SessionMessagingKit/Database/Migrations/_006_SMK_InitialSetupMigration.swift index 2823930730..6c9e861ca9 100644 --- a/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_006_SMK_InitialSetupMigration.swift @@ -6,9 +6,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _001_InitialSetupMigration: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "initialSetup" +enum _006_SMK_InitialSetupMigration: Migration { + static let identifier: String = "messagingKit.initialSetup" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [ Contact.self, Profile.self, SessionThread.self, DisappearingMessagesConfiguration.self, @@ -59,7 +58,7 @@ enum _001_InitialSetupMigration: Migration { /// Create a full-text search table synchronized with the Profile table try db.create(virtualTable: "profile_fts", using: FTS5()) { t in t.synchronize(withTable: "profile") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("nickname") t.column("name") @@ -106,7 +105,7 @@ enum _001_InitialSetupMigration: Migration { /// Create a full-text search table synchronized with the ClosedGroup table try db.create(virtualTable: "closedGroup_fts", using: FTS5()) { t in t.synchronize(withTable: "closedGroup") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("name") } @@ -157,7 +156,7 @@ enum _001_InitialSetupMigration: Migration { /// Create a full-text search table synchronized with the OpenGroup table try db.create(virtualTable: "openGroup_fts", using: FTS5()) { t in t.synchronize(withTable: "openGroup") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("name") } @@ -268,7 +267,7 @@ enum _001_InitialSetupMigration: Migration { /// Create a full-text search table synchronized with the Interaction table try db.create(virtualTable: "interaction_fts", using: FTS5()) { t in t.synchronize(withTable: "interaction") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("body") } diff --git a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionMessagingKit/Database/Migrations/_007_SMK_SetupStandardJobs.swift similarity index 92% rename from SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift rename to SessionMessagingKit/Database/Migrations/_007_SMK_SetupStandardJobs.swift index bfcdbea5d3..f7057035e0 100644 --- a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionMessagingKit/Database/Migrations/_007_SMK_SetupStandardJobs.swift @@ -3,13 +3,12 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit /// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration /// before running the `YDBToGRDBMigration` -enum _002_SetupStandardJobs: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "SetupStandardJobs" +enum _007_SMK_SetupStandardJobs: Migration { + static let identifier: String = "messagingKit.SetupStandardJobs" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_008_SNK_YDBToGRDBMigration.swift similarity index 69% rename from SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift rename to SessionMessagingKit/Database/Migrations/_008_SNK_YDBToGRDBMigration.swift index 565fd3e3f2..ac66dd7c0b 100644 --- a/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_008_SNK_YDBToGRDBMigration.swift @@ -2,10 +2,10 @@ import Foundation import GRDB +import SessionUtilitiesKit -enum _003_YDBToGRDBMigration: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "YDBToGRDBMigration" +enum _008_SNK_YDBToGRDBMigration: Migration { + static let identifier: String = "snodeKit.YDBToGRDBMigration" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift b/SessionMessagingKit/Database/Migrations/_009_SMK_YDBToGRDBMigration.swift similarity index 79% rename from SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift rename to SessionMessagingKit/Database/Migrations/_009_SMK_YDBToGRDBMigration.swift index 13ecc5df76..7851f48d74 100644 --- a/SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_009_SMK_YDBToGRDBMigration.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _003_YDBToGRDBMigration: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "YDBToGRDBMigration" +enum _009_SMK_YDBToGRDBMigration: Migration { + static let identifier: String = "messagingKit.YDBToGRDBMigration" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift b/SessionMessagingKit/Database/Migrations/_010_FlagMessageHashAsDeletedOrInvalid.swift similarity index 82% rename from SessionSnodeKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift rename to SessionMessagingKit/Database/Migrations/_010_FlagMessageHashAsDeletedOrInvalid.swift index 486665167c..ff71d5ffbe 100644 --- a/SessionSnodeKit/Database/Migrations/_004_FlagMessageHashAsDeletedOrInvalid.swift +++ b/SessionMessagingKit/Database/Migrations/_010_FlagMessageHashAsDeletedOrInvalid.swift @@ -7,9 +7,8 @@ import SessionUtilitiesKit /// This migration adds a flag to the `SnodeReceivedMessageInfo` so that when deleting interactions we can /// ignore their hashes when subsequently trying to fetch new messages (which results in the storage server returning /// messages from the beginning of time) -enum _004_FlagMessageHashAsDeletedOrInvalid: Migration { - static let target: TargetMigrations.Identifier = .snodeKit - static let identifier: String = "FlagMessageHashAsDeletedOrInvalid" +enum _010_FlagMessageHashAsDeletedOrInvalid: Migration { + static let identifier: String = "snodeKit.FlagMessageHashAsDeletedOrInvalid" static let minExpectedRunDuration: TimeInterval = 0.2 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift b/SessionMessagingKit/Database/Migrations/_011_RemoveLegacyYDB.swift similarity index 75% rename from SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift rename to SessionMessagingKit/Database/Migrations/_011_RemoveLegacyYDB.swift index 07db8962d8..ef8588451f 100644 --- a/SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift +++ b/SessionMessagingKit/Database/Migrations/_011_RemoveLegacyYDB.swift @@ -3,12 +3,11 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit /// This migration used to remove the legacy YapDatabase files (the old logic has been removed and is no longer supported so it now does nothing) -enum _004_RemoveLegacyYDB: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "RemoveLegacyYDB" +enum _011_RemoveLegacyYDB: Migration { + static let identifier: String = "messagingKit.RemoveLegacyYDB" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift b/SessionMessagingKit/Database/Migrations/_012_AddJobPriority.swift similarity index 90% rename from SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift rename to SessionMessagingKit/Database/Migrations/_012_AddJobPriority.swift index 852033b582..93a2c68752 100644 --- a/SessionUtilitiesKit/Database/Migrations/_004_AddJobPriority.swift +++ b/SessionMessagingKit/Database/Migrations/_012_AddJobPriority.swift @@ -2,10 +2,10 @@ import Foundation import GRDB +import SessionUtilitiesKit -enum _004_AddJobPriority: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "AddJobPriority" +enum _012_AddJobPriority: Migration { + static let identifier: String = "utilitiesKit.AddJobPriority" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_005_FixDeletedMessageReadState.swift b/SessionMessagingKit/Database/Migrations/_013_FixDeletedMessageReadState.swift similarity index 83% rename from SessionMessagingKit/Database/Migrations/_005_FixDeletedMessageReadState.swift rename to SessionMessagingKit/Database/Migrations/_013_FixDeletedMessageReadState.swift index efe33c321d..a49d63d0ca 100644 --- a/SessionMessagingKit/Database/Migrations/_005_FixDeletedMessageReadState.swift +++ b/SessionMessagingKit/Database/Migrations/_013_FixDeletedMessageReadState.swift @@ -5,9 +5,8 @@ import GRDB import SessionUtilitiesKit /// This migration fixes a bug where certain message variants could incorrectly be counted as unread messages -enum _005_FixDeletedMessageReadState: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "FixDeletedMessageReadState" +enum _013_FixDeletedMessageReadState: Migration { + static let identifier: String = "messagingKit.FixDeletedMessageReadState" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_006_FixHiddenModAdminSupport.swift b/SessionMessagingKit/Database/Migrations/_014_FixHiddenModAdminSupport.swift similarity index 85% rename from SessionMessagingKit/Database/Migrations/_006_FixHiddenModAdminSupport.swift rename to SessionMessagingKit/Database/Migrations/_014_FixHiddenModAdminSupport.swift index 006b04c283..5247ae2d79 100644 --- a/SessionMessagingKit/Database/Migrations/_006_FixHiddenModAdminSupport.swift +++ b/SessionMessagingKit/Database/Migrations/_014_FixHiddenModAdminSupport.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// This migration fixes an issue where hidden mods/admins weren't getting recognised as mods/admins, it reset's the `info_updates` /// for open groups so they will fully re-fetch their mod/admin lists -enum _006_FixHiddenModAdminSupport: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "FixHiddenModAdminSupport" +enum _014_FixHiddenModAdminSupport: Migration { + static let identifier: String = "messagingKit.FixHiddenModAdminSupport" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_007_HomeQueryOptimisationIndexes.swift b/SessionMessagingKit/Database/Migrations/_015_HomeQueryOptimisationIndexes.swift similarity index 81% rename from SessionMessagingKit/Database/Migrations/_007_HomeQueryOptimisationIndexes.swift rename to SessionMessagingKit/Database/Migrations/_015_HomeQueryOptimisationIndexes.swift index bf8ded493e..3a0a617928 100644 --- a/SessionMessagingKit/Database/Migrations/_007_HomeQueryOptimisationIndexes.swift +++ b/SessionMessagingKit/Database/Migrations/_015_HomeQueryOptimisationIndexes.swift @@ -7,9 +7,8 @@ import GRDB import SessionUtilitiesKit /// This migration adds an index to the interaction table in order to improve the performance of retrieving the number of unread interactions -enum _007_HomeQueryOptimisationIndexes: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "HomeQueryOptimisationIndexes" +enum _015_HomeQueryOptimisationIndexes: Migration { + static let identifier: String = "messagingKit.HomeQueryOptimisationIndexes" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/Session/Database/Migrations/_001_ThemePreferences.swift b/SessionMessagingKit/Database/Migrations/_016_ThemePreferences.swift similarity index 78% rename from Session/Database/Migrations/_001_ThemePreferences.swift rename to SessionMessagingKit/Database/Migrations/_016_ThemePreferences.swift index 8bb9987f95..f19020e3ed 100644 --- a/Session/Database/Migrations/_001_ThemePreferences.swift +++ b/SessionMessagingKit/Database/Migrations/_016_ThemePreferences.swift @@ -13,9 +13,8 @@ import SessionUtilitiesKit /// **Note:** This migration used to live within `SessionUIKit` but we wanted to isolate it and remove dependencies from it so we /// needed to extract this migration into the `Session` and `SessionShareExtension` targets (since both need theming they both /// need to provide this migration as an option during setup) -enum _001_ThemePreferences: Migration { - static let target: TargetMigrations.Identifier = ._deprecatedUIKit - static let identifier: String = "ThemePreferences" +enum _016_ThemePreferences: Migration { + static let identifier: String = "uiKit.ThemePreferences" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] @@ -86,25 +85,3 @@ private extension Theme.PrimaryColor { } } } - -enum DeprecatedUIKitMigrationTarget: MigratableTarget { - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: ._deprecatedUIKit, - migrations: [ - // Want to ensure the initial DB stuff has been completed before doing any - // SNUIKit migrations - [], // Initial DB Creation - [], // YDB to GRDB Migration - [], // Legacy DB removal - [ - _001_ThemePreferences.self - ], // Add job priorities - [], // Fix thread FTS - [], - [], // Renamed `Setting` to `KeyValueStore` - [] - ] - ) - } -} diff --git a/SessionMessagingKit/Database/Migrations/_008_EmojiReacts.swift b/SessionMessagingKit/Database/Migrations/_017_EmojiReacts.swift similarity index 91% rename from SessionMessagingKit/Database/Migrations/_008_EmojiReacts.swift rename to SessionMessagingKit/Database/Migrations/_017_EmojiReacts.swift index bcd9c2f84b..c102846bad 100644 --- a/SessionMessagingKit/Database/Migrations/_008_EmojiReacts.swift +++ b/SessionMessagingKit/Database/Migrations/_017_EmojiReacts.swift @@ -5,9 +5,8 @@ import GRDB import SessionUtilitiesKit /// This migration adds the new types needed for Emoji Reacts -enum _008_EmojiReacts: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "EmojiReacts" +enum _017_EmojiReacts: Migration { + static let identifier: String = "messagingKit.EmojiReacts" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [Reaction.self] diff --git a/SessionMessagingKit/Database/Migrations/_009_OpenGroupPermission.swift b/SessionMessagingKit/Database/Migrations/_018_OpenGroupPermission.swift similarity index 83% rename from SessionMessagingKit/Database/Migrations/_009_OpenGroupPermission.swift rename to SessionMessagingKit/Database/Migrations/_018_OpenGroupPermission.swift index b8e7c47efb..bf51074058 100644 --- a/SessionMessagingKit/Database/Migrations/_009_OpenGroupPermission.swift +++ b/SessionMessagingKit/Database/Migrations/_018_OpenGroupPermission.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _009_OpenGroupPermission: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "OpenGroupPermission" +enum _018_OpenGroupPermission: Migration { + static let identifier: String = "messagingKit.OpenGroupPermission" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_010_AddThreadIdToFTS.swift b/SessionMessagingKit/Database/Migrations/_019_AddThreadIdToFTS.swift similarity index 82% rename from SessionMessagingKit/Database/Migrations/_010_AddThreadIdToFTS.swift rename to SessionMessagingKit/Database/Migrations/_019_AddThreadIdToFTS.swift index 9c2aea1207..92dfecc4fd 100644 --- a/SessionMessagingKit/Database/Migrations/_010_AddThreadIdToFTS.swift +++ b/SessionMessagingKit/Database/Migrations/_019_AddThreadIdToFTS.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// This migration recreates the interaction FTS table and adds the threadId so we can do a performant in-conversation /// searh (currently it's much slower than the global search) -enum _010_AddThreadIdToFTS: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "AddThreadIdToFTS" +enum _019_AddThreadIdToFTS: Migration { + static let identifier: String = "messagingKit.AddThreadIdToFTS" static let minExpectedRunDuration: TimeInterval = 3 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] @@ -22,7 +21,7 @@ enum _010_AddThreadIdToFTS: Migration { try db.create(virtualTable: "interaction_fts", using: FTS5()) { t in t.synchronize(withTable: "interaction") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("body") t.column("threadId") diff --git a/SessionUtilitiesKit/Database/Migrations/_005_AddJobUniqueHash.swift b/SessionMessagingKit/Database/Migrations/_020_AddJobUniqueHash.swift similarity index 76% rename from SessionUtilitiesKit/Database/Migrations/_005_AddJobUniqueHash.swift rename to SessionMessagingKit/Database/Migrations/_020_AddJobUniqueHash.swift index e4a36701f5..b9bebf7e81 100644 --- a/SessionUtilitiesKit/Database/Migrations/_005_AddJobUniqueHash.swift +++ b/SessionMessagingKit/Database/Migrations/_020_AddJobUniqueHash.swift @@ -2,10 +2,10 @@ import Foundation import GRDB +import SessionUtilitiesKit -enum _005_AddJobUniqueHash: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "AddJobUniqueHash" +enum _020_AddJobUniqueHash: Migration { + static let identifier: String = "utilitiesKit.AddJobUniqueHash" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift b/SessionMessagingKit/Database/Migrations/_021_AddSnodeReveivedMessageInfoPrimaryKey.swift similarity index 91% rename from SessionSnodeKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift rename to SessionMessagingKit/Database/Migrations/_021_AddSnodeReveivedMessageInfoPrimaryKey.swift index acc361590d..55e215871e 100644 --- a/SessionSnodeKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift +++ b/SessionMessagingKit/Database/Migrations/_021_AddSnodeReveivedMessageInfoPrimaryKey.swift @@ -2,12 +2,12 @@ import Foundation import GRDB +import SessionNetworkingKit import SessionUtilitiesKit /// This migration adds a primary key to `SnodeReceivedMessageInfo` based on the key and hash to speed up lookup -enum _005_AddSnodeReveivedMessageInfoPrimaryKey: Migration { - static let target: TargetMigrations.Identifier = .snodeKit - static let identifier: String = "AddSnodeReveivedMessageInfoPrimaryKey" +enum _021_AddSnodeReveivedMessageInfoPrimaryKey: Migration { + static let identifier: String = "snodeKit.AddSnodeReveivedMessageInfoPrimaryKey" static let minExpectedRunDuration: TimeInterval = 0.2 static let createdTables: [(TableRecord & FetchableRecord).Type] = [SnodeReceivedMessageInfo.self] diff --git a/SessionSnodeKit/Database/Migrations/_006_DropSnodeCache.swift b/SessionMessagingKit/Database/Migrations/_022_DropSnodeCache.swift similarity index 86% rename from SessionSnodeKit/Database/Migrations/_006_DropSnodeCache.swift rename to SessionMessagingKit/Database/Migrations/_022_DropSnodeCache.swift index b2a3d41bd2..af5ceaaa5d 100644 --- a/SessionSnodeKit/Database/Migrations/_006_DropSnodeCache.swift +++ b/SessionMessagingKit/Database/Migrations/_022_DropSnodeCache.swift @@ -5,9 +5,8 @@ import GRDB import SessionUtilitiesKit /// This migration drops the current `SnodePool` and `SnodeSet` and their associated jobs as they are handled by `libSession` now -enum _006_DropSnodeCache: Migration { - static let target: TargetMigrations.Identifier = .snodeKit - static let identifier: String = "DropSnodeCache" +enum _022_DropSnodeCache: Migration { + static let identifier: String = "snodeKit.DropSnodeCache" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionSnodeKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift b/SessionMessagingKit/Database/Migrations/_023_SplitSnodeReceivedMessageInfo.swift similarity index 95% rename from SessionSnodeKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift rename to SessionMessagingKit/Database/Migrations/_023_SplitSnodeReceivedMessageInfo.swift index aa74f45ff4..91746c8cef 100644 --- a/SessionSnodeKit/Database/Migrations/_007_SplitSnodeReceivedMessageInfo.swift +++ b/SessionMessagingKit/Database/Migrations/_023_SplitSnodeReceivedMessageInfo.swift @@ -2,12 +2,12 @@ import Foundation import GRDB +import SessionNetworkingKit import SessionUtilitiesKit /// This migration splits the old `key` structure used for `SnodeReceivedMessageInfo` into separate columns for more efficient querying -enum _007_SplitSnodeReceivedMessageInfo: Migration { - static let target: TargetMigrations.Identifier = .snodeKit - static let identifier: String = "SplitSnodeReceivedMessageInfo" +enum _023_SplitSnodeReceivedMessageInfo: Migration { + static let identifier: String = "snodeKit.SplitSnodeReceivedMessageInfo" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [SnodeReceivedMessageInfo.self] @@ -91,10 +91,10 @@ enum _007_SplitSnodeReceivedMessageInfo: Migration { let targetNamespace: Int = { guard swarmPublicKeySplitComponents.count == 2 else { - return SnodeAPI.Namespace.default.rawValue + return Network.SnodeAPI.Namespace.default.rawValue } - return (Int(swarmPublicKeySplitComponents[1]) ?? SnodeAPI.Namespace.default.rawValue) + return (Int(swarmPublicKeySplitComponents[1]) ?? Network.SnodeAPI.Namespace.default.rawValue) }() let wasDeletedOrInvalid: Bool? = info["wasDeletedOrInvalid"] diff --git a/SessionSnodeKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift b/SessionMessagingKit/Database/Migrations/_024_ResetUserConfigLastHashes.swift similarity index 63% rename from SessionSnodeKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift rename to SessionMessagingKit/Database/Migrations/_024_ResetUserConfigLastHashes.swift index 468ee6999c..60052a5eda 100644 --- a/SessionSnodeKit/Database/Migrations/_008_ResetUserConfigLastHashes.swift +++ b/SessionMessagingKit/Database/Migrations/_024_ResetUserConfigLastHashes.swift @@ -2,20 +2,20 @@ import Foundation import GRDB +import SessionNetworkingKit import SessionUtilitiesKit /// This migration resets the `lastHash` value for all user config namespaces to force the app to fetch the latest config /// messages in case there are multi-part config message we had previously seen and failed to merge -enum _008_ResetUserConfigLastHashes: Migration { - static let target: TargetMigrations.Identifier = .snodeKit - static let identifier: String = "ResetUserConfigLastHashes" +enum _024_ResetUserConfigLastHashes: Migration { + static let identifier: String = "snodeKit.ResetUserConfigLastHashes" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] static func migrate(_ db: ObservingDatabase, using dependencies: Dependencies) throws { try db.execute(literal: """ DELETE FROM snodeReceivedMessageInfo - WHERE namespace IN (\(SnodeAPI.Namespace.configContacts.rawValue), \(SnodeAPI.Namespace.configUserProfile.rawValue), \(SnodeAPI.Namespace.configUserGroups.rawValue), \(SnodeAPI.Namespace.configConvoInfoVolatile.rawValue)) + WHERE namespace IN (\(Network.SnodeAPI.Namespace.configContacts.rawValue), \(Network.SnodeAPI.Namespace.configUserProfile.rawValue), \(Network.SnodeAPI.Namespace.configUserGroups.rawValue), \(Network.SnodeAPI.Namespace.configConvoInfoVolatile.rawValue)) """) MigrationExecution.updateProgress(1) diff --git a/SessionMessagingKit/Database/Migrations/_011_AddPendingReadReceipts.swift b/SessionMessagingKit/Database/Migrations/_025_AddPendingReadReceipts.swift similarity index 89% rename from SessionMessagingKit/Database/Migrations/_011_AddPendingReadReceipts.swift rename to SessionMessagingKit/Database/Migrations/_025_AddPendingReadReceipts.swift index 5f51432095..0b0d5fec63 100644 --- a/SessionMessagingKit/Database/Migrations/_011_AddPendingReadReceipts.swift +++ b/SessionMessagingKit/Database/Migrations/_025_AddPendingReadReceipts.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// This migration adds a table to track pending read receipts (it's possible to receive a read receipt message before getting the original /// message due to how one-to-one conversations work, by storing pending read receipts we should be able to prevent this case) -enum _011_AddPendingReadReceipts: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "AddPendingReadReceipts" +enum _025_AddPendingReadReceipts: Migration { + static let identifier: String = "messagingKit.AddPendingReadReceipts" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [PendingReadReceipt.self] diff --git a/SessionMessagingKit/Database/Migrations/_012_AddFTSIfNeeded.swift b/SessionMessagingKit/Database/Migrations/_026_AddFTSIfNeeded.swift similarity index 80% rename from SessionMessagingKit/Database/Migrations/_012_AddFTSIfNeeded.swift rename to SessionMessagingKit/Database/Migrations/_026_AddFTSIfNeeded.swift index a030deed3f..b655432c2c 100644 --- a/SessionMessagingKit/Database/Migrations/_012_AddFTSIfNeeded.swift +++ b/SessionMessagingKit/Database/Migrations/_026_AddFTSIfNeeded.swift @@ -5,9 +5,8 @@ import GRDB import SessionUtilitiesKit /// This migration adds the FTS table back for internal test users whose FTS table was removed unintentionally -enum _012_AddFTSIfNeeded: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "AddFTSIfNeeded" +enum _026_AddFTSIfNeeded: Migration { + static let identifier: String = "messagingKit.AddFTSIfNeeded" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] @@ -17,7 +16,7 @@ enum _012_AddFTSIfNeeded: Migration { if try db.tableExists("interaction_fts") == false { try db.create(virtualTable: "interaction_fts", using: FTS5()) { t in t.synchronize(withTable: "interaction") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("body") t.column("threadId") diff --git a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift b/SessionMessagingKit/Database/Migrations/_027_SessionUtilChanges.swift similarity index 98% rename from SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift rename to SessionMessagingKit/Database/Migrations/_027_SessionUtilChanges.swift index cb67ad5bf5..57e76b93a9 100644 --- a/SessionMessagingKit/Database/Migrations/_013_SessionUtilChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_027_SessionUtilChanges.swift @@ -9,9 +9,8 @@ import SessionUtil import SessionUtilitiesKit /// This migration makes the neccessary changes to support the updated user config syncing system -enum _013_SessionUtilChanges: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "SessionUtilChanges" +enum _027_SessionUtilChanges: Migration { + static let identifier: String = "messagingKit.SessionUtilChanges" static let minExpectedRunDuration: TimeInterval = 0.4 static let createdTables: [(TableRecord & FetchableRecord).Type] = [ConfigDump.self] @@ -229,7 +228,7 @@ enum _013_SessionUtilChanges: Migration { } } -private extension _013_SessionUtilChanges { +private extension _027_SessionUtilChanges { static func generateLegacyClosedGroupKeyPairHash(threadId: String, publicKey: Data, secretKey: Data) -> String { return Data(Insecure.MD5 .hash(data: threadId.bytes + publicKey.bytes + secretKey.bytes) diff --git a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift b/SessionMessagingKit/Database/Migrations/_028_GenerateInitialUserConfigDumps.swift similarity index 98% rename from SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift rename to SessionMessagingKit/Database/Migrations/_028_GenerateInitialUserConfigDumps.swift index c81a813e12..a53031dd46 100644 --- a/SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift +++ b/SessionMessagingKit/Database/Migrations/_028_GenerateInitialUserConfigDumps.swift @@ -6,9 +6,8 @@ import SessionUtil import SessionUtilitiesKit /// This migration goes through the current state of the database and generates config dumps for the user config types -enum _014_GenerateInitialUserConfigDumps: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "GenerateInitialUserConfigDumps" +enum _028_GenerateInitialUserConfigDumps: Migration { + static let identifier: String = "messagingKit.GenerateInitialUserConfigDumps" static let minExpectedRunDuration: TimeInterval = 4.0 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift b/SessionMessagingKit/Database/Migrations/_029_BlockCommunityMessageRequests.swift similarity index 94% rename from SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift rename to SessionMessagingKit/Database/Migrations/_029_BlockCommunityMessageRequests.swift index dd58e13355..22cb579ef3 100644 --- a/SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift +++ b/SessionMessagingKit/Database/Migrations/_029_BlockCommunityMessageRequests.swift @@ -5,9 +5,8 @@ import GRDB import SessionUtilitiesKit /// This migration adds a flag indicating whether a profile has indicated it is blocking community message requests -enum _015_BlockCommunityMessageRequests: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "BlockCommunityMessageRequests" +enum _029_BlockCommunityMessageRequests: Migration { + static let identifier: String = "messagingKit.BlockCommunityMessageRequests" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_016_MakeBrokenProfileTimestampsNullable.swift b/SessionMessagingKit/Database/Migrations/_030_MakeBrokenProfileTimestampsNullable.swift similarity index 89% rename from SessionMessagingKit/Database/Migrations/_016_MakeBrokenProfileTimestampsNullable.swift rename to SessionMessagingKit/Database/Migrations/_030_MakeBrokenProfileTimestampsNullable.swift index 82816602ca..dbbeb35044 100644 --- a/SessionMessagingKit/Database/Migrations/_016_MakeBrokenProfileTimestampsNullable.swift +++ b/SessionMessagingKit/Database/Migrations/_030_MakeBrokenProfileTimestampsNullable.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// This migration updates the tiemstamps added to the `Profile` in earlier migrations to be nullable (having it not null /// results in migration issues when a user jumps between multiple versions) -enum _016_MakeBrokenProfileTimestampsNullable: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "MakeBrokenProfileTimestampsNullable" +enum _030_MakeBrokenProfileTimestampsNullable: Migration { + static let identifier: String = "messagingKit.MakeBrokenProfileTimestampsNullable" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_017_RebuildFTSIfNeeded_2_4_5.swift b/SessionMessagingKit/Database/Migrations/_031_RebuildFTSIfNeeded_2_4_5.swift similarity index 85% rename from SessionMessagingKit/Database/Migrations/_017_RebuildFTSIfNeeded_2_4_5.swift rename to SessionMessagingKit/Database/Migrations/_031_RebuildFTSIfNeeded_2_4_5.swift index c9c9240fde..9660270f21 100644 --- a/SessionMessagingKit/Database/Migrations/_017_RebuildFTSIfNeeded_2_4_5.swift +++ b/SessionMessagingKit/Database/Migrations/_031_RebuildFTSIfNeeded_2_4_5.swift @@ -7,9 +7,8 @@ import GRDB import SessionUtilitiesKit /// This migration adds the FTS table back if either the tables or any of the triggers no longer exist -enum _017_RebuildFTSIfNeeded_2_4_5: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "RebuildFTSIfNeeded_2_4_5" +enum _031_RebuildFTSIfNeeded_2_4_5: Migration { + static let identifier: String = "messagingKit.RebuildFTSIfNeeded_2_4_5" static let minExpectedRunDuration: TimeInterval = 0.01 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] @@ -30,7 +29,7 @@ enum _017_RebuildFTSIfNeeded_2_4_5: Migration { try db.create(virtualTable: "interaction_fts", using: FTS5()) { t in t.synchronize(withTable: "interaction") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("body") t.column("threadId") @@ -44,7 +43,7 @@ enum _017_RebuildFTSIfNeeded_2_4_5: Migration { try db.create(virtualTable: "profile_fts", using: FTS5()) { t in t.synchronize(withTable: "profile") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("nickname") t.column("name") @@ -58,7 +57,7 @@ enum _017_RebuildFTSIfNeeded_2_4_5: Migration { try db.create(virtualTable: "closedGroup_fts", using: FTS5()) { t in t.synchronize(withTable: "closedGroup") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("name") } @@ -71,7 +70,7 @@ enum _017_RebuildFTSIfNeeded_2_4_5: Migration { try db.create(virtualTable: "openGroup_fts", using: FTS5()) { t in t.synchronize(withTable: "openGroup") - t.tokenizer = _001_InitialSetupMigration.fullTextSearchTokenizer + t.tokenizer = _006_SMK_InitialSetupMigration.fullTextSearchTokenizer t.column("name") } diff --git a/SessionMessagingKit/Database/Migrations/_018_DisappearingMessagesConfiguration.swift b/SessionMessagingKit/Database/Migrations/_032_DisappearingMessagesConfiguration.swift similarity index 97% rename from SessionMessagingKit/Database/Migrations/_018_DisappearingMessagesConfiguration.swift rename to SessionMessagingKit/Database/Migrations/_032_DisappearingMessagesConfiguration.swift index 809c426e56..4bf07b018f 100644 --- a/SessionMessagingKit/Database/Migrations/_018_DisappearingMessagesConfiguration.swift +++ b/SessionMessagingKit/Database/Migrations/_032_DisappearingMessagesConfiguration.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _018_DisappearingMessagesConfiguration: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "DisappearingMessagesWithTypes" +enum _032_DisappearingMessagesConfiguration: Migration { + static let identifier: String = "messagingKit.DisappearingMessagesWithTypes" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_019_ScheduleAppUpdateCheckJob.swift b/SessionMessagingKit/Database/Migrations/_033_ScheduleAppUpdateCheckJob.swift similarity index 84% rename from SessionMessagingKit/Database/Migrations/_019_ScheduleAppUpdateCheckJob.swift rename to SessionMessagingKit/Database/Migrations/_033_ScheduleAppUpdateCheckJob.swift index 9f5bd4c724..f0df877278 100644 --- a/SessionMessagingKit/Database/Migrations/_019_ScheduleAppUpdateCheckJob.swift +++ b/SessionMessagingKit/Database/Migrations/_033_ScheduleAppUpdateCheckJob.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _019_ScheduleAppUpdateCheckJob: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "ScheduleAppUpdateCheckJob" +enum _033_ScheduleAppUpdateCheckJob: Migration { + static let identifier: String = "messagingKit.ScheduleAppUpdateCheckJob" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_020_AddMissingWhisperFlag.swift b/SessionMessagingKit/Database/Migrations/_034_AddMissingWhisperFlag.swift similarity index 81% rename from SessionMessagingKit/Database/Migrations/_020_AddMissingWhisperFlag.swift rename to SessionMessagingKit/Database/Migrations/_034_AddMissingWhisperFlag.swift index 90dbfc4fbd..ecd158b8b1 100644 --- a/SessionMessagingKit/Database/Migrations/_020_AddMissingWhisperFlag.swift +++ b/SessionMessagingKit/Database/Migrations/_034_AddMissingWhisperFlag.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _020_AddMissingWhisperFlag: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "AddMissingWhisperFlag" +enum _034_AddMissingWhisperFlag: Migration { + static let identifier: String = "messagingKit.AddMissingWhisperFlag" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_021_ReworkRecipientState.swift b/SessionMessagingKit/Database/Migrations/_035_ReworkRecipientState.swift similarity index 97% rename from SessionMessagingKit/Database/Migrations/_021_ReworkRecipientState.swift rename to SessionMessagingKit/Database/Migrations/_035_ReworkRecipientState.swift index a47d202666..f0ebe35f20 100644 --- a/SessionMessagingKit/Database/Migrations/_021_ReworkRecipientState.swift +++ b/SessionMessagingKit/Database/Migrations/_035_ReworkRecipientState.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _021_ReworkRecipientState: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "ReworkRecipientState" +enum _035_ReworkRecipientState: Migration { + static let identifier: String = "messagingKit.ReworkRecipientState" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] @@ -180,7 +179,7 @@ enum _021_ReworkRecipientState: Migration { } } -private extension _021_ReworkRecipientState { +private extension _035_ReworkRecipientState { enum LegacyState: Int { case sending case failed diff --git a/SessionMessagingKit/Database/Migrations/_022_GroupsRebuildChanges.swift b/SessionMessagingKit/Database/Migrations/_036_GroupsRebuildChanges.swift similarity index 89% rename from SessionMessagingKit/Database/Migrations/_022_GroupsRebuildChanges.swift rename to SessionMessagingKit/Database/Migrations/_036_GroupsRebuildChanges.swift index 11894d2d79..55cb03346f 100644 --- a/SessionMessagingKit/Database/Migrations/_022_GroupsRebuildChanges.swift +++ b/SessionMessagingKit/Database/Migrations/_036_GroupsRebuildChanges.swift @@ -5,12 +5,11 @@ import Foundation import UIKit.UIImage import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit -enum _022_GroupsRebuildChanges: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "GroupsRebuildChanges" +enum _036_GroupsRebuildChanges: Migration { + static let identifier: String = "messagingKit.GroupsRebuildChanges" static let minExpectedRunDuration: TimeInterval = 0.1 static var createdTables: [(FetchableRecord & TableRecord).Type] = [] @@ -145,19 +144,24 @@ enum _022_GroupsRebuildChanges: Migration { /// If the group isn't in the invited state then make sure to subscribe for PNs once the migrations are done if !group.invited, let token: String = dependencies[defaults: .standard, key: .deviceToken] { - db.afterCommit { - dependencies[singleton: .storage] - .readPublisher { db in - try PushNotificationAPI.preparedSubscribe( - db, + let maybeAuthMethod: AuthenticationMethod? = try? Authentication.with( + db, + swarmPublicKey: group.groupSessionId, + using: dependencies + ) + + if let authMethod: AuthenticationMethod = maybeAuthMethod { + db.afterCommit { + try? Network.PushNotification + .preparedSubscribe( token: Data(hex: token), - sessionIds: [SessionId(.group, hex: group.groupSessionId)], + swarms: [(SessionId(.group, hex: group.groupSessionId), authMethod)], using: dependencies ) - } - .flatMap { $0.send(using: dependencies) } - .subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies) - .sinkUntilComplete() + .send(using: dependencies) + .subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies) + .sinkUntilComplete() + } } } } @@ -212,7 +216,7 @@ enum _022_GroupsRebuildChanges: Migration { } } -private extension _022_GroupsRebuildChanges { +private extension _036_GroupsRebuildChanges { static func generateFilename(format: ImageFormat = .jpeg, using dependencies: Dependencies) -> String { return dependencies[singleton: .crypto] .generate(.uuid()) diff --git a/SessionMessagingKit/Database/Migrations/_023_GroupsExpiredFlag.swift b/SessionMessagingKit/Database/Migrations/_037_GroupsExpiredFlag.swift similarity index 76% rename from SessionMessagingKit/Database/Migrations/_023_GroupsExpiredFlag.swift rename to SessionMessagingKit/Database/Migrations/_037_GroupsExpiredFlag.swift index 2bffc37639..294efd4846 100644 --- a/SessionMessagingKit/Database/Migrations/_023_GroupsExpiredFlag.swift +++ b/SessionMessagingKit/Database/Migrations/_037_GroupsExpiredFlag.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _023_GroupsExpiredFlag: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "GroupsExpiredFlag" +enum _037_GroupsExpiredFlag: Migration { + static let identifier: String = "messagingKit.GroupsExpiredFlag" static let minExpectedRunDuration: TimeInterval = 0.1 static var createdTables: [(FetchableRecord & TableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_024_FixBustedInteractionVariant.swift b/SessionMessagingKit/Database/Migrations/_038_FixBustedInteractionVariant.swift similarity index 83% rename from SessionMessagingKit/Database/Migrations/_024_FixBustedInteractionVariant.swift rename to SessionMessagingKit/Database/Migrations/_038_FixBustedInteractionVariant.swift index 9b65965362..5929ad6c87 100644 --- a/SessionMessagingKit/Database/Migrations/_024_FixBustedInteractionVariant.swift +++ b/SessionMessagingKit/Database/Migrations/_038_FixBustedInteractionVariant.swift @@ -7,9 +7,8 @@ import SessionUtilitiesKit /// There was a bug with internal releases of the Groups Rebuild feature where we incorrectly assigned an `Interaction.Variant` /// value of `3` to deleted message artifacts when it should have been `2`, this migration updates any interactions with a value of `2` /// to be `3` -enum _024_FixBustedInteractionVariant: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "FixBustedInteractionVariant" +enum _038_FixBustedInteractionVariant: Migration { + static let identifier: String = "messagingKit.FixBustedInteractionVariant" static let minExpectedRunDuration: TimeInterval = 0.1 static var createdTables: [(FetchableRecord & TableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_025_DropLegacyClosedGroupKeyPairTable.swift b/SessionMessagingKit/Database/Migrations/_039_DropLegacyClosedGroupKeyPairTable.swift similarity index 74% rename from SessionMessagingKit/Database/Migrations/_025_DropLegacyClosedGroupKeyPairTable.swift rename to SessionMessagingKit/Database/Migrations/_039_DropLegacyClosedGroupKeyPairTable.swift index afc0dd376d..20111dade4 100644 --- a/SessionMessagingKit/Database/Migrations/_025_DropLegacyClosedGroupKeyPairTable.swift +++ b/SessionMessagingKit/Database/Migrations/_039_DropLegacyClosedGroupKeyPairTable.swift @@ -6,9 +6,8 @@ import SessionUtilitiesKit /// Legacy closed groups are no longer supported so we can drop the `closedGroupKeyPair` table from /// the database -enum _025_DropLegacyClosedGroupKeyPairTable: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "DropLegacyClosedGroupKeyPairTable" +enum _039_DropLegacyClosedGroupKeyPairTable: Migration { + static let identifier: String = "messagingKit.DropLegacyClosedGroupKeyPairTable" static let minExpectedRunDuration: TimeInterval = 0.1 static var createdTables: [(FetchableRecord & TableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_026_MessageDeduplicationTable.swift b/SessionMessagingKit/Database/Migrations/_040_MessageDeduplicationTable.swift similarity index 98% rename from SessionMessagingKit/Database/Migrations/_026_MessageDeduplicationTable.swift rename to SessionMessagingKit/Database/Migrations/_040_MessageDeduplicationTable.swift index fd993ac86b..5431858f19 100644 --- a/SessionMessagingKit/Database/Migrations/_026_MessageDeduplicationTable.swift +++ b/SessionMessagingKit/Database/Migrations/_040_MessageDeduplicationTable.swift @@ -3,14 +3,13 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit /// The different platforms use different approaches for message deduplication but in the future we want to shift the database logic into /// `libSession` so it makes sense to try to define a longer-term deduplication approach we we can use in `libSession`, additonally /// the PN extension will need to replicate this deduplication data so having a single source-of-truth for the data will make things easier -enum _026_MessageDeduplicationTable: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "MessageDeduplicationTable" +enum _040_MessageDeduplicationTable: Migration { + static let identifier: String = "messagingKit.MessageDeduplicationTable" static let minExpectedRunDuration: TimeInterval = 5 static var createdTables: [(FetchableRecord & TableRecord).Type] = [ MessageDeduplication.self @@ -343,7 +342,7 @@ enum _026_MessageDeduplicationTable: Migration { } } -internal extension _026_MessageDeduplicationTable { +internal extension _040_MessageDeduplicationTable { static func legacyDedupeIdentifier( variant: Interaction.Variant, timestampMs: Int64 @@ -372,7 +371,7 @@ internal extension _026_MessageDeduplicationTable { } } -internal extension _026_MessageDeduplicationTable { +internal extension _040_MessageDeduplicationTable { enum ControlMessageProcessRecordVariant: Int { case readReceipt = 1 case typingIndicator = 2 diff --git a/SessionUtilitiesKit/Database/Migrations/_006_RenameTableSettingToKeyValueStore.swift b/SessionMessagingKit/Database/Migrations/_041_RenameTableSettingToKeyValueStore.swift similarity index 68% rename from SessionUtilitiesKit/Database/Migrations/_006_RenameTableSettingToKeyValueStore.swift rename to SessionMessagingKit/Database/Migrations/_041_RenameTableSettingToKeyValueStore.swift index ffad28e128..48a366aecf 100644 --- a/SessionUtilitiesKit/Database/Migrations/_006_RenameTableSettingToKeyValueStore.swift +++ b/SessionMessagingKit/Database/Migrations/_041_RenameTableSettingToKeyValueStore.swift @@ -2,10 +2,10 @@ import Foundation import GRDB +import SessionUtilitiesKit -enum _006_RenameTableSettingToKeyValueStore: Migration { - static let target: TargetMigrations.Identifier = .utilitiesKit - static let identifier: String = "RenameTableSettingToKeyValueStore" // stringlint:disable +enum _041_RenameTableSettingToKeyValueStore: Migration { + static let identifier: String = "utilitiesKit.RenameTableSettingToKeyValueStore" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [ KeyValueStore.self ] diff --git a/SessionMessagingKit/Database/Migrations/_027_MoveSettingsToLibSession.swift b/SessionMessagingKit/Database/Migrations/_042_MoveSettingsToLibSession.swift similarity index 97% rename from SessionMessagingKit/Database/Migrations/_027_MoveSettingsToLibSession.swift rename to SessionMessagingKit/Database/Migrations/_042_MoveSettingsToLibSession.swift index 3f6353e72c..5e63bd1bff 100644 --- a/SessionMessagingKit/Database/Migrations/_027_MoveSettingsToLibSession.swift +++ b/SessionMessagingKit/Database/Migrations/_042_MoveSettingsToLibSession.swift @@ -6,9 +6,8 @@ import SessionUIKit import SessionUtilitiesKit /// This migration extracts an old settings from the database and saves them into libSession -enum _027_MoveSettingsToLibSession: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "MoveSettingsToLibSession" +enum _042_MoveSettingsToLibSession: Migration { + static let identifier: String = "messagingKit.MoveSettingsToLibSession" static let minExpectedRunDuration: TimeInterval = 0.1 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_028_RenameAttachments.swift b/SessionMessagingKit/Database/Migrations/_043_RenameAttachments.swift similarity index 98% rename from SessionMessagingKit/Database/Migrations/_028_RenameAttachments.swift rename to SessionMessagingKit/Database/Migrations/_043_RenameAttachments.swift index 2c92c21d11..ff94b962c2 100644 --- a/SessionMessagingKit/Database/Migrations/_028_RenameAttachments.swift +++ b/SessionMessagingKit/Database/Migrations/_043_RenameAttachments.swift @@ -3,14 +3,13 @@ import Foundation import UniformTypeIdentifiers import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit /// This migration renames all attachments to use a hash of the download url for the filename instead of a random UUID (means we can /// generate the filename just from the URL and don't need to store the filename) -enum _028_RenameAttachments: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "RenameAttachments" +enum _043_RenameAttachments: Migration { + static let identifier: String = "messagingKit.RenameAttachments" static let minExpectedRunDuration: TimeInterval = 3 static let createdTables: [(TableRecord & FetchableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Migrations/_029_AddProMessageFlag.swift b/SessionMessagingKit/Database/Migrations/_044_AddProMessageFlag.swift similarity index 76% rename from SessionMessagingKit/Database/Migrations/_029_AddProMessageFlag.swift rename to SessionMessagingKit/Database/Migrations/_044_AddProMessageFlag.swift index 0d2751199e..7f51d1ffc2 100644 --- a/SessionMessagingKit/Database/Migrations/_029_AddProMessageFlag.swift +++ b/SessionMessagingKit/Database/Migrations/_044_AddProMessageFlag.swift @@ -4,9 +4,8 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _029_AddProMessageFlag: Migration { - static let target: TargetMigrations.Identifier = .messagingKit - static let identifier: String = "AddProMessageFlag" +enum _044_AddProMessageFlag: Migration { + static let identifier: String = "messagingKit.AddProMessageFlag" static let minExpectedRunDuration: TimeInterval = 0.1 static var createdTables: [(FetchableRecord & TableRecord).Type] = [] diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index 4110492f4c..072306a4d3 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -7,7 +7,7 @@ import Combine import UniformTypeIdentifiers import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUIKit public struct Attachment: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { @@ -489,7 +489,7 @@ extension Attachment { /// **Note:** We need to continue to send this because it seems that the Desktop client _does_ in fact still use this /// id for downloading attachments. Desktop will be updated to remove it's use but in order to fix attachments for old /// versions we set this value again - let legacyId: UInt64 = (Attachment.fileId(for: self.downloadUrl).map { UInt64($0) } ?? 0) + let legacyId: UInt64 = (Network.FileServer.fileId(for: self.downloadUrl).map { UInt64($0) } ?? 0) let builder = SNProtoAttachmentPointer.builder(id: legacyId) builder.setContentType(contentType) @@ -689,14 +689,4 @@ extension Attachment { return true } - - public static func fileId(for downloadUrl: String?) -> String? { - return downloadUrl - .map { urlString -> String? in - urlString - .split(separator: "/") // stringlint:ignore - .last - .map { String($0) } - } - } } diff --git a/SessionMessagingKit/Database/Models/ClosedGroup.swift b/SessionMessagingKit/Database/Models/ClosedGroup.swift index ce182b7a81..da2655eb59 100644 --- a/SessionMessagingKit/Database/Models/ClosedGroup.swift +++ b/SessionMessagingKit/Database/Models/ClosedGroup.swift @@ -5,7 +5,7 @@ import Combine import GRDB import DifferenceKit import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public struct ClosedGroup: Codable, Equatable, Hashable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { @@ -226,16 +226,22 @@ public extension ClosedGroup { /// Subscribe for group push notifications if let token: String = dependencies[defaults: .standard, key: .deviceToken] { - try? PushNotificationAPI - .preparedSubscribe( - db, - token: Data(hex: token), - sessionIds: [SessionId(.group, hex: group.id)], - using: dependencies - ) - .send(using: dependencies) - .subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies) - .sinkUntilComplete() + let maybeAuthMethod: AuthenticationMethod? = try? Authentication.with( + db, + swarmPublicKey: group.id, + using: dependencies + ) + + if let authMethod: AuthenticationMethod = maybeAuthMethod { + try? Network.PushNotification + .preparedSubscribe( + token: Data(hex: token), + swarms: [(SessionId(.group, hex: group.id), authMethod)], + using: dependencies + ) + .send(using: dependencies) + .sinkUntilComplete() + } } } @@ -305,13 +311,20 @@ public extension ClosedGroup { /// Bulk unsubscripe from updated groups being removed if dataToRemove.contains(.pushNotifications) && threadVariants.contains(where: { $0.variant == .group }) { if let token: String = dependencies[defaults: .standard, key: .deviceToken] { - try? PushNotificationAPI + try? Network.PushNotification .preparedUnsubscribe( - db, token: Data(hex: token), - sessionIds: threadVariants + swarms: threadVariants .filter { $0.variant == .group } - .map { SessionId(.group, hex: $0.id) }, + .compactMap { info in + let authMethod: AuthenticationMethod? = try? Authentication.with( + db, + swarmPublicKey: info.id, + using: dependencies + ) + + return authMethod.map { (SessionId(.group, hex: info.id), $0) } + }, using: dependencies ) .send(using: dependencies) diff --git a/SessionMessagingKit/Database/Models/ConfigDump.swift b/SessionMessagingKit/Database/Models/ConfigDump.swift index b264051429..eed9528aa9 100644 --- a/SessionMessagingKit/Database/Models/ConfigDump.swift +++ b/SessionMessagingKit/Database/Models/ConfigDump.swift @@ -4,7 +4,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public struct ConfigDump: Codable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { @@ -86,7 +86,7 @@ public extension ConfigDump.Variant { .groupInfo, .groupMembers, .groupKeys ] - init(namespace: SnodeAPI.Namespace) { + init(namespace: Network.SnodeAPI.Namespace) { switch namespace { case .configUserProfile: self = .userProfile case .configContacts: self = .contacts @@ -104,19 +104,19 @@ public extension ConfigDump.Variant { /// Config messages should last for 30 days rather than the standard 14 var ttl: UInt64 { 30 * 24 * 60 * 60 * 1000 } - var namespace: SnodeAPI.Namespace { + var namespace: Network.SnodeAPI.Namespace { switch self { - case .userProfile: return SnodeAPI.Namespace.configUserProfile - case .contacts: return SnodeAPI.Namespace.configContacts - case .convoInfoVolatile: return SnodeAPI.Namespace.configConvoInfoVolatile - case .userGroups: return SnodeAPI.Namespace.configUserGroups - case .local: return SnodeAPI.Namespace.configLocal + case .userProfile: return Network.SnodeAPI.Namespace.configUserProfile + case .contacts: return Network.SnodeAPI.Namespace.configContacts + case .convoInfoVolatile: return Network.SnodeAPI.Namespace.configConvoInfoVolatile + case .userGroups: return Network.SnodeAPI.Namespace.configUserGroups + case .local: return Network.SnodeAPI.Namespace.configLocal - case .groupInfo: return SnodeAPI.Namespace.configGroupInfo - case .groupMembers: return SnodeAPI.Namespace.configGroupMembers - case .groupKeys: return SnodeAPI.Namespace.configGroupKeys + case .groupInfo: return Network.SnodeAPI.Namespace.configGroupInfo + case .groupMembers: return Network.SnodeAPI.Namespace.configGroupMembers + case .groupKeys: return Network.SnodeAPI.Namespace.configGroupKeys - case .invalid: return SnodeAPI.Namespace.unknown + case .invalid: return Network.SnodeAPI.Namespace.unknown } } diff --git a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift index de46275da9..10e9faf495 100644 --- a/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift +++ b/SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift @@ -5,7 +5,7 @@ import GRDB import SessionUIKit import SessionUtil import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public struct DisappearingMessagesConfiguration: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "disappearingMessagesConfiguration" } diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index 06ca54b819..43b0dfdcb7 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public struct Interaction: Codable, Identifiable, Equatable, Hashable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "interaction" } diff --git a/SessionMessagingKit/Database/Models/LinkPreview.swift b/SessionMessagingKit/Database/Models/LinkPreview.swift index 51bfb22b48..0c8cada73f 100644 --- a/SessionMessagingKit/Database/Models/LinkPreview.swift +++ b/SessionMessagingKit/Database/Models/LinkPreview.swift @@ -7,7 +7,7 @@ import Combine import UniformTypeIdentifiers import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public struct LinkPreview: Codable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "linkPreview" } diff --git a/SessionMessagingKit/Database/Models/MessageDeduplication.swift b/SessionMessagingKit/Database/Models/MessageDeduplication.swift index 15269d25b2..e8b5a8f531 100644 --- a/SessionMessagingKit/Database/Models/MessageDeduplication.swift +++ b/SessionMessagingKit/Database/Models/MessageDeduplication.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category @@ -209,7 +209,7 @@ public extension MessageDeduplication { _ processedMessage: ProcessedMessage, using dependencies: Dependencies ) throws { - typealias Variant = _026_MessageDeduplicationTable.ControlMessageProcessRecordVariant + typealias Variant = _040_MessageDeduplicationTable.ControlMessageProcessRecordVariant try ensureMessageIsNotADuplicate( threadId: processedMessage.threadId, uniqueIdentifier: processedMessage.uniqueIdentifier, @@ -402,12 +402,12 @@ private extension MessageDeduplication { _ db: ObservingDatabase, threadId: String, legacyIdentifier: String?, - legacyVariant: _026_MessageDeduplicationTable.ControlMessageProcessRecordVariant?, + legacyVariant: _040_MessageDeduplicationTable.ControlMessageProcessRecordVariant?, timestampMs: Int64?, serverExpirationTimestamp: TimeInterval?, using dependencies: Dependencies ) throws { - typealias Variant = _026_MessageDeduplicationTable.ControlMessageProcessRecordVariant + typealias Variant = _040_MessageDeduplicationTable.ControlMessageProcessRecordVariant guard let legacyIdentifier: String = legacyIdentifier, let legacyVariant: Variant = legacyVariant, @@ -463,7 +463,7 @@ private extension MessageDeduplication { } @available(*, deprecated, message: "⚠️ Remove this code once once enough time has passed since it's release (at least 1 month)") - static func getLegacyVariant(for variant: Message.Variant?) -> _026_MessageDeduplicationTable.ControlMessageProcessRecordVariant? { + static func getLegacyVariant(for variant: Message.Variant?) -> _040_MessageDeduplicationTable.ControlMessageProcessRecordVariant? { guard let variant: Message.Variant = variant else { return nil } switch variant { @@ -494,7 +494,7 @@ private extension MessageDeduplication { case .standard(_, _, _, let messageInfo, _): guard let timestampMs: UInt64 = messageInfo.message.sentTimestampMs, - let variant: _026_MessageDeduplicationTable.ControlMessageProcessRecordVariant = getLegacyVariant(for: Message.Variant(from: messageInfo.message)) + let variant: _040_MessageDeduplicationTable.ControlMessageProcessRecordVariant = getLegacyVariant(for: Message.Variant(from: messageInfo.message)) else { return nil } return "LegacyRecord-\(variant.rawValue)-\(timestampMs)" // stringlint:ignore diff --git a/SessionMessagingKit/Database/Models/OpenGroup.swift b/SessionMessagingKit/Database/Models/OpenGroup.swift index f6ac676fea..977887c1a1 100644 --- a/SessionMessagingKit/Database/Models/OpenGroup.swift +++ b/SessionMessagingKit/Database/Models/OpenGroup.swift @@ -4,6 +4,7 @@ import Foundation import GRDB +import SessionNetworkingKit import SessionUtilitiesKit public struct OpenGroup: Codable, Equatable, Hashable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { @@ -40,7 +41,7 @@ public struct OpenGroup: Codable, Equatable, Hashable, Identifiable, FetchableRe self.rawValue = rawValue } - public init(roomInfo: OpenGroupAPI.RoomPollInfo) { + public init(roomInfo: Network.SOGS.RoomPollInfo) { var permissions: Permissions = [] if roomInfo.read { permissions.insert(.read) } diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index 5a8c10ae42..c3893cbeb1 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public struct SessionThread: Codable, Identifiable, Equatable, Hashable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible, IdentifiableTableRecord { public static var databaseTableName: String { "thread" } diff --git a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift index 0127ebc746..210d629967 100644 --- a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public enum AttachmentDownloadJob: JobExecutor { public static var maxFailureCount: Int = 3 @@ -112,7 +112,7 @@ public enum AttachmentDownloadJob: JobExecutor { switch maybeRoomToken { case .some(let roomToken): - return try OpenGroupAPI + return try Network.SOGS .preparedDownload( url: info.downloadUrl, roomToken: roomToken, diff --git a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift index d1dc85ddea..a3cd20c22a 100644 --- a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/CheckForAppUpdatesJob.swift b/SessionMessagingKit/Jobs/CheckForAppUpdatesJob.swift index c3f4583f59..64f9c9f4c7 100644 --- a/SessionMessagingKit/Jobs/CheckForAppUpdatesJob.swift +++ b/SessionMessagingKit/Jobs/CheckForAppUpdatesJob.swift @@ -2,7 +2,7 @@ import Foundation import Combine -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift b/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift index 5720b9acfb..53cd2a73a5 100644 --- a/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/ConfigMessageReceiveJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category @@ -92,13 +92,13 @@ extension ConfigMessageReceiveJob { case data } - public let namespace: SnodeAPI.Namespace + public let namespace: Network.SnodeAPI.Namespace public let serverHash: String public let serverTimestampMs: Int64 public let data: Data public init( - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, serverHash: String, serverTimestampMs: Int64, data: Data diff --git a/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift b/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift index dc37ac0c02..f1266f72a1 100644 --- a/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift +++ b/SessionMessagingKit/Jobs/ConfigurationSyncJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category @@ -96,14 +96,14 @@ public enum ConfigurationSyncJob: JobExecutor { try Authentication.with(db, swarmPublicKey: swarmPublicKey, using: dependencies) } .tryFlatMap { authMethod -> AnyPublisher<(ResponseInfoType, Network.BatchResponse), Error> in - try SnodeAPI.preparedSequence( + try Network.SnodeAPI.preparedSequence( requests: [] .appending(contentsOf: additionalTransientData?.beforeSequenceRequests) .appending( contentsOf: try pendingPushes.pushData .flatMap { pushData -> [ErasedPreparedRequest] in try pushData.data.map { data -> ErasedPreparedRequest in - try SnodeAPI + try Network.SnodeAPI .preparedSendMessage( message: SnodeMessage( recipient: swarmPublicKey, @@ -121,7 +121,7 @@ public enum ConfigurationSyncJob: JobExecutor { .appending(try { guard !pendingPushes.obsoleteHashes.isEmpty else { return nil } - return try SnodeAPI.preparedDeleteMessages( + return try Network.SnodeAPI.preparedDeleteMessages( serverHashes: Array(pendingPushes.obsoleteHashes), requireSuccessfulDeletion: false, authMethod: authMethod, diff --git a/SessionMessagingKit/Jobs/DisappearingMessagesJob.swift b/SessionMessagingKit/Jobs/DisappearingMessagesJob.swift index 14b34fe97d..e6689d73b0 100644 --- a/SessionMessagingKit/Jobs/DisappearingMessagesJob.swift +++ b/SessionMessagingKit/Jobs/DisappearingMessagesJob.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift b/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift index ec9ee87bc3..edae3c0108 100644 --- a/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift +++ b/SessionMessagingKit/Jobs/DisplayPictureDownloadJob.swift @@ -6,7 +6,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category @@ -39,7 +39,7 @@ public enum DisplayPictureDownloadJob: JobExecutor { switch details.target { case .profile(_, let url, _), .group(_, let url, _): guard - let fileId: String = Attachment.fileId(for: url), + let fileId: String = Network.FileServer.fileId(for: url), let downloadUrl: URL = URL(string: Network.FileServer.downloadUrlString(for: url, fileId: fileId)) else { throw NetworkError.invalidURL } @@ -48,16 +48,17 @@ public enum DisplayPictureDownloadJob: JobExecutor { using: dependencies ) - case .community(let fileId, let roomToken, let server): + case .community(let fileId, let roomToken, let server, let skipAuthentication): guard let info: LibSession.OpenGroupCapabilityInfo = try? LibSession.OpenGroupCapabilityInfo .fetchOne(db, id: OpenGroup.idFor(roomToken: roomToken, server: server)) else { throw JobRunnerError.missingRequiredDetails } - return try OpenGroupAPI.preparedDownload( + return try Network.SOGS.preparedDownload( fileId: fileId, roomToken: roomToken, authMethod: Authentication.community(info: info), + skipAuthentication: skipAuthentication, using: dependencies ) } @@ -206,7 +207,7 @@ public enum DisplayPictureDownloadJob: JobExecutor { ) db.addConversationEvent(id: id, type: .updated(.displayPictureUrl(url))) - case .community(_, let roomToken, let server): + case .community(_, let roomToken, let server, _): _ = try? OpenGroup .filter(id: OpenGroup.idFor(roomToken: roomToken, server: server)) .updateAllAndConfig( @@ -228,18 +229,18 @@ extension DisplayPictureDownloadJob { public enum Target: Codable, Hashable, CustomStringConvertible { case profile(id: String, url: String, encryptionKey: Data) case group(id: String, url: String, encryptionKey: Data) - case community(imageId: String, roomToken: String, server: String) + case community(imageId: String, roomToken: String, server: String, skipAuthentication: Bool = false) var isValid: Bool { switch self { case .profile(_, let url, let encryptionKey), .group(_, let url, let encryptionKey): return ( !url.isEmpty && - Attachment.fileId(for: url) != nil && + Network.FileServer.fileId(for: url) != nil && encryptionKey.count == DisplayPictureManager.aes256KeyByteLength ) - case .community(let imageId, _, _): return !imageId.isEmpty + case .community(let imageId, _, _, _): return !imageId.isEmpty } } @@ -249,7 +250,7 @@ extension DisplayPictureDownloadJob { switch self { case .profile(let id, _, _): return "profile: \(id)" case .group(let id, _, _): return "group: \(id)" - case .community(_, let roomToken, let server): return "room: \(roomToken) on server: \(server)" + case .community(_, let roomToken, let server, _): return "room: \(roomToken) on server: \(server)" } } } @@ -274,11 +275,12 @@ extension DisplayPictureDownloadJob { self.target = { switch target { - case .community(let imageId, let roomToken, let server): + case .community(let imageId, let roomToken, let server, let skipAuthentication): return .community( imageId: imageId, roomToken: roomToken, - server: server.lowercased() // Always in lowercase on `OpenGroup` + server: server.lowercased(), // Always in lowercase on `OpenGroup` + skipAuthentication: skipAuthentication ) default: return target @@ -358,7 +360,7 @@ extension DisplayPictureDownloadJob { return (url == latestDisplayPictureUrl) - case .community(let imageId, let roomToken, let server): + case .community(let imageId, let roomToken, let server, _): guard let latestImageId: String = try? OpenGroup .select(.imageId) diff --git a/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift b/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift index 0ffd051f5a..ef3cdb9d7a 100644 --- a/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift +++ b/SessionMessagingKit/Jobs/ExpirationUpdateJob.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit public enum ExpirationUpdateJob: JobExecutor { public static var maxFailureCount: Int = -1 @@ -26,7 +26,7 @@ public enum ExpirationUpdateJob: JobExecutor { dependencies[singleton: .storage] .readPublisher { db in - try SnodeAPI + try Network.SnodeAPI .preparedUpdateExpiry( serverHashes: details.serverHashes, updatedExpiryMs: details.expirationTimestampMs, diff --git a/SessionMessagingKit/Jobs/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/GarbageCollectionJob.swift index 5629508213..aacaa48e5c 100644 --- a/SessionMessagingKit/Jobs/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/GarbageCollectionJob.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category @@ -182,7 +182,7 @@ public enum GarbageCollectionJob: JobExecutor { LEFT JOIN \(SessionThread.self) ON \(thread[.id]) = \(openGroup[.threadId]) WHERE ( \(thread[.id]) IS NULL AND - \(SQL("\(openGroup[.server]) != \(OpenGroupAPI.defaultServer.lowercased())")) + \(SQL("\(openGroup[.server]) != \(Network.SOGS.defaultServer.lowercased())")) ) ) """) diff --git a/SessionMessagingKit/Jobs/GetExpirationJob.swift b/SessionMessagingKit/Jobs/GetExpirationJob.swift index e367edd0df..00be1730b7 100644 --- a/SessionMessagingKit/Jobs/GetExpirationJob.swift +++ b/SessionMessagingKit/Jobs/GetExpirationJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public enum GetExpirationJob: JobExecutor { @@ -39,7 +39,7 @@ public enum GetExpirationJob: JobExecutor { dependencies[singleton: .storage] .readPublisher { db -> Network.PreparedRequest in - try SnodeAPI.preparedGetExpiries( + try Network.SnodeAPI.preparedGetExpiries( of: expirationInfo.map { $0.key }, authMethod: try Authentication.with( db, diff --git a/SessionMessagingKit/Jobs/GroupInviteMemberJob.swift b/SessionMessagingKit/Jobs/GroupInviteMemberJob.swift index b40827c6e8..e652f75f13 100644 --- a/SessionMessagingKit/Jobs/GroupInviteMemberJob.swift +++ b/SessionMessagingKit/Jobs/GroupInviteMemberJob.swift @@ -5,7 +5,7 @@ import Combine import GRDB import SessionUIKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/GroupLeavingJob.swift b/SessionMessagingKit/Jobs/GroupLeavingJob.swift index 475a5ea13d..208072da67 100644 --- a/SessionMessagingKit/Jobs/GroupLeavingJob.swift +++ b/SessionMessagingKit/Jobs/GroupLeavingJob.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category @@ -92,7 +92,7 @@ public enum GroupLeavingJob: JobExecutor { .tryFlatMap { requestType -> AnyPublisher in switch requestType { case .sendLeaveMessage(let authMethod, let disappearingConfig): - return try SnodeAPI + return try Network.SnodeAPI .preparedBatch( requests: [ /// Don't expire the `GroupUpdateMemberLeftMessage` as that's not a UI-based diff --git a/SessionMessagingKit/Jobs/GroupPromoteMemberJob.swift b/SessionMessagingKit/Jobs/GroupPromoteMemberJob.swift index 10a934451b..d46639ac78 100644 --- a/SessionMessagingKit/Jobs/GroupPromoteMemberJob.swift +++ b/SessionMessagingKit/Jobs/GroupPromoteMemberJob.swift @@ -5,7 +5,7 @@ import Combine import GRDB import SessionUIKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Jobs/MessageSendJob.swift b/SessionMessagingKit/Jobs/MessageSendJob.swift index 590a660a46..bbb2b1c7fe 100644 --- a/SessionMessagingKit/Jobs/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/MessageSendJob.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category @@ -352,7 +352,7 @@ public extension MessageSendJob { .compactMap { info in guard let attachment: Attachment = attachments[info.attachmentId], - let fileId: String = Attachment.fileId(for: info.downloadUrl) + let fileId: String = Network.FileServer.fileId(for: info.downloadUrl) else { return nil } return (attachment, fileId) diff --git a/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift b/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift index 3decb55cf9..0253242bc7 100644 --- a/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift +++ b/SessionMessagingKit/Jobs/ProcessPendingGroupMemberRemovalsJob.swift @@ -6,7 +6,7 @@ import GRDB import SessionUtil import SessionUIKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category @@ -97,7 +97,7 @@ public enum ProcessPendingGroupMemberRemovalsJob: JobExecutor { .tryMap { _ -> Network.PreparedRequest in /// Revoke the members authData from the group so the server rejects API calls from the ex-members (fire-and-forget /// this request, we don't want it to be blocking) - let preparedRevokeSubaccounts: Network.PreparedRequest = try SnodeAPI.preparedRevokeSubaccounts( + let preparedRevokeSubaccounts: Network.PreparedRequest = try Network.SnodeAPI.preparedRevokeSubaccounts( subaccountsToRevoke: try dependencies.mutate(cache: .libSession) { cache in try Array(pendingRemovals.keys).map { memberId in try dependencies[singleton: .crypto].tryGenerate( @@ -131,7 +131,7 @@ public enum ProcessPendingGroupMemberRemovalsJob: JobExecutor { domain: .kickedMessage ) ) - let preparedGroupDeleteMessage: Network.PreparedRequest = try SnodeAPI + let preparedGroupDeleteMessage: Network.PreparedRequest = try Network.SnodeAPI .preparedSendMessage( message: SnodeMessage( recipient: groupSessionId.hexString, @@ -179,7 +179,7 @@ public enum ProcessPendingGroupMemberRemovalsJob: JobExecutor { }() /// Combine the two requests to be sent at the same time - return try SnodeAPI.preparedSequence( + return try Network.SnodeAPI.preparedSequence( requests: [preparedRevokeSubaccounts, preparedGroupDeleteMessage, preparedMemberContentRemovalMessage] .compactMap { $0 }, requireAllBatchResponses: true, @@ -262,7 +262,7 @@ public enum ProcessPendingGroupMemberRemovalsJob: JobExecutor { ) /// Delete the messages from the swarm so users won't download them again - try? SnodeAPI + try? Network.SnodeAPI .preparedDeleteMessages( serverHashes: Array(hashes), requireSuccessfulDeletion: false, diff --git a/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift b/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift index a864b6c2fe..e34c48694b 100644 --- a/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift +++ b/SessionMessagingKit/Jobs/RetrieveDefaultOpenGroupRoomsJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category @@ -40,17 +40,17 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { .isEmpty else { return deferred(job) } - // The OpenGroupAPI won't make any API calls if there is no entry for an OpenGroup + // The Network.SOGS won't make any API calls if there is no entry for an OpenGroup // in the database so we need to create a dummy one to retrieve the default room data - let defaultGroupId: String = OpenGroup.idFor(roomToken: "", server: OpenGroupAPI.defaultServer) + let defaultGroupId: String = OpenGroup.idFor(roomToken: "", server: Network.SOGS.defaultServer) dependencies[singleton: .storage].write { db in guard try OpenGroup.exists(db, id: defaultGroupId) == false else { return } try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "", userCount: 0, @@ -64,14 +64,15 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { .readPublisher { [dependencies] db -> AuthenticationMethod in try Authentication.with( db, - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, activeOnly: false, /// The record for the default rooms is inactive using: dependencies ) } - .tryFlatMap { [dependencies] authMethod -> AnyPublisher<(ResponseInfoType, OpenGroupAPI.CapabilitiesAndRoomsResponse), Error> in - try OpenGroupAPI.preparedCapabilitiesAndRooms( + .tryFlatMap { [dependencies] authMethod -> AnyPublisher<(ResponseInfoType, Network.SOGS.CapabilitiesAndRoomsResponse), Error> in + try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: authMethod, + skipAuthentication: true, using: dependencies ).send(using: dependencies) } @@ -96,11 +97,11 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { OpenGroupManager.handleCapabilities( db, capabilities: response.capabilities.data, - on: OpenGroupAPI.defaultServer + on: Network.SOGS.defaultServer ) let existingImageIds: [String: String] = try OpenGroup - .filter(OpenGroup.Columns.server == OpenGroupAPI.defaultServer) + .filter(OpenGroup.Columns.server == Network.SOGS.defaultServer) .filter(OpenGroup.Columns.imageId != nil) .fetchAll(db) .reduce(into: [:]) { result, next in result[next.id] = next.imageId } @@ -112,9 +113,9 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { return ( room, try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: room.token, - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: room.name, roomDescription: room.roomDescription, @@ -131,7 +132,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { db, id: OpenGroup.idFor( roomToken: room.token, - server: OpenGroupAPI.defaultServer + server: Network.SOGS.defaultServer ) ) .map { (room, $0) } @@ -140,7 +141,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { /// Schedule the room image download (if it doesn't match out current one) result.forEach { room, openGroup in - let openGroupId: String = OpenGroup.idFor(roomToken: room.token, server: OpenGroupAPI.defaultServer) + let openGroupId: String = OpenGroup.idFor(roomToken: room.token, server: Network.SOGS.defaultServer) guard let imageId: String = room.imageId, @@ -157,7 +158,8 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { target: .community( imageId: imageId, roomToken: room.token, - server: OpenGroupAPI.defaultServer + server: Network.SOGS.defaultServer, + skipAuthentication: true ), timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000) ) diff --git a/SessionMessagingKit/Jobs/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/SendReadReceiptsJob.swift index 1da0c5a95c..9196301bfc 100644 --- a/SessionMessagingKit/Jobs/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/SendReadReceiptsJob.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public enum SendReadReceiptsJob: JobExecutor { diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift index b5b3f0d65e..82cdc5060c 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupInfo.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtil -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Size Restrictions @@ -293,7 +293,7 @@ internal extension LibSessionCacheType { swarmPublicKey: groupSessionId.hexString, using: dependencies )).map { authMethod in - try? SnodeAPI + try? Network.SnodeAPI .preparedDeleteMessages( serverHashes: Array(messageHashesToDelete), requireSuccessfulDeletion: false, diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupMembers.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupMembers.swift index 51215e9344..8774ca003d 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupMembers.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupMembers.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtil -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Size Restrictions diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+Shared.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+Shared.swift index 522957def1..ce4d1986dd 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+Shared.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+Shared.swift @@ -3,7 +3,7 @@ import UIKit import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtil import SessionUtilitiesKit diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift index 54908b4470..64a1f5260a 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift @@ -3,7 +3,7 @@ import UIKit import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtil import SessionUtilitiesKit diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+UserGroups.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+UserGroups.swift index 27f7bf7fdf..c9724f15b1 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+UserGroups.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+UserGroups.swift @@ -4,7 +4,7 @@ import Foundation import GRDB import SessionUtil import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Size Restrictions diff --git a/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift b/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift index 8bbd82e93b..217af7d5ae 100644 --- a/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift +++ b/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUIKit import SessionUtil import SessionUtilitiesKit diff --git a/SessionMessagingKit/LibSession/Types/Config.swift b/SessionMessagingKit/LibSession/Types/Config.swift index 52c8057ef5..7eb71d798f 100644 --- a/SessionMessagingKit/LibSession/Types/Config.swift +++ b/SessionMessagingKit/LibSession/Types/Config.swift @@ -4,7 +4,7 @@ import Foundation import SessionUtil -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public extension LibSession { @@ -324,7 +324,7 @@ public extension LibSession { .sorted() if successfulMergeTimestamps.count != messages.count { - Log.warn(.libSession, "Unable to merge \(SnodeAPI.Namespace.configGroupKeys) messages (\(successfulMergeTimestamps.count)/\(messages.count))") + Log.warn(.libSession, "Unable to merge \(Network.SnodeAPI.Namespace.configGroupKeys) messages (\(successfulMergeTimestamps.count)/\(messages.count))") } return successfulMergeTimestamps.last diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index b7d393a02c..a61a43ec3d 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public extension Message { @@ -39,7 +39,7 @@ public extension Message { } } - public var defaultNamespace: SnodeAPI.Namespace? { + public var defaultNamespace: Network.SnodeAPI.Namespace? { switch self { case .contact, .syncMessage: return .`default` case .closedGroup(let groupId) where (try? SessionId.Prefix(from: groupId)) == .group: @@ -61,7 +61,7 @@ public extension Message { if prefix == .blinded15 || prefix == .blinded25 { guard let lookup: BlindedIdLookup = try? BlindedIdLookup.fetchOne(db, id: threadId) else { - throw OpenGroupAPIError.blindedLookupMissingCommunityInfo + throw SOGSError.blindedLookupMissingCommunityInfo } return .openGroupInbox( diff --git a/SessionMessagingKit/Messages/Message+DisappearingMessages.swift b/SessionMessagingKit/Messages/Message+DisappearingMessages.swift index d511a374fc..4212f74901 100644 --- a/SessionMessagingKit/Messages/Message+DisappearingMessages.swift +++ b/SessionMessagingKit/Messages/Message+DisappearingMessages.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUIKit import SessionUtilitiesKit diff --git a/SessionMessagingKit/Messages/Message+Origin.swift b/SessionMessagingKit/Messages/Message+Origin.swift index 0e0cedf2f2..1c0d5f330a 100644 --- a/SessionMessagingKit/Messages/Message+Origin.swift +++ b/SessionMessagingKit/Messages/Message+Origin.swift @@ -1,14 +1,14 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public extension Message { enum Origin: Codable, Hashable { case swarm( publicKey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, serverHash: String, serverTimestampMs: Int64, serverExpirationTimestamp: TimeInterval diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index c640978d0d..2583d84d7c 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit /// Abstract base class for `VisibleMessage` and `ControlMessage`. @@ -83,7 +83,7 @@ public class Message: Codable { case (false, .some(let sigTimestampMs), .some): let delta: TimeInterval = (TimeInterval(max(sigTimestampMs, sentTimestampMs) - min(sigTimestampMs, sentTimestampMs)) / 1000) - return delta < OpenGroupAPI.validTimestampVarianceThreshold + return delta < Network.SOGS.validTimestampVarianceThreshold // FIXME: We want to remove support for this case in a future release case (_, .none, _): return true @@ -174,7 +174,7 @@ public enum ProcessedMessage { ) case config( publicKey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, serverHash: String, serverTimestampMs: Int64, data: Data, @@ -190,7 +190,7 @@ public enum ProcessedMessage { } } - var namespace: SnodeAPI.Namespace { + var namespace: Network.SnodeAPI.Namespace { switch self { case .standard(_, let threadVariant, _, _, _): switch threadVariant { @@ -432,12 +432,12 @@ public extension Message { static func processRawReceivedReactions( _ db: ObservingDatabase, openGroupId: String, - message: OpenGroupAPI.Message, - associatedPendingChanges: [OpenGroupAPI.PendingChange], + message: Network.SOGS.Message, + associatedPendingChanges: [OpenGroupManager.PendingChange], using dependencies: Dependencies ) -> [Reaction] { guard - let reactions: [String: OpenGroupAPI.Message.Reaction] = message.reactions, + let reactions: [String: Network.SOGS.Message.Reaction] = message.reactions, let openGroupCapabilityInfo: LibSession.OpenGroupCapabilityInfo = try? LibSession.OpenGroupCapabilityInfo .fetchOne(db, id: openGroupId) else { return [] } @@ -483,7 +483,7 @@ public extension Message { let pendingChangeSelfReaction: Bool? = { // Find the newest 'PendingChange' entry with a matching emoji, if one exists, and // set the "self reaction" value based on it's action - let maybePendingChange: OpenGroupAPI.PendingChange? = associatedPendingChanges + let maybePendingChange: OpenGroupManager.PendingChange? = associatedPendingChanges .sorted(by: { lhs, rhs -> Bool in (lhs.seqNo ?? Int64.max) >= (rhs.seqNo ?? Int64.max) }) .first { pendingChange in if case .reaction(_, let emoji, _) = pendingChange.metadata { @@ -495,7 +495,7 @@ public extension Message { // If there is no pending change for this reaction then return nil guard - let pendingChange: OpenGroupAPI.PendingChange = maybePendingChange, + let pendingChange: OpenGroupManager.PendingChange = maybePendingChange, case .reaction(_, _, let action) = pendingChange.metadata else { return nil } diff --git a/SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroup.swift b/SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroup.swift new file mode 100644 index 0000000000..2ff8f76fc6 --- /dev/null +++ b/SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroup.swift @@ -0,0 +1,94 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtil +import SessionUtilitiesKit + +// MARK: - Messages + +public extension Crypto.Generator { + static func ciphertextWithSessionBlindingProtocol( + plaintext: Data, + recipientBlindedId: String, + serverPublicKey: String + ) -> Crypto.Generator { + return Crypto.Generator( + id: "ciphertextWithSessionBlindingProtocol", + args: [plaintext, serverPublicKey] + ) { dependencies in + var cPlaintext: [UInt8] = Array(plaintext) + var cEd25519SecretKey: [UInt8] = dependencies[cache: .general].ed25519SecretKey + var cRecipientBlindedId: [UInt8] = Array(Data(hex: recipientBlindedId)) + var cServerPublicKey: [UInt8] = Array(Data(hex: serverPublicKey)) + var maybeCiphertext: UnsafeMutablePointer? = nil + var ciphertextLen: Int = 0 + + guard !cEd25519SecretKey.isEmpty else { throw MessageSenderError.noUserED25519KeyPair } + guard + cEd25519SecretKey.count == 64, + cServerPublicKey.count == 32, + session_encrypt_for_blinded_recipient( + &cPlaintext, + cPlaintext.count, + &cEd25519SecretKey, + &cServerPublicKey, + &cRecipientBlindedId, + &maybeCiphertext, + &ciphertextLen + ), + ciphertextLen > 0, + let ciphertext: Data = maybeCiphertext.map({ Data(bytes: $0, count: ciphertextLen) }) + else { throw MessageSenderError.encryptionFailed } + + free(UnsafeMutableRawPointer(mutating: maybeCiphertext)) + + return ciphertext + } + } + + static func plaintextWithSessionBlindingProtocol( + ciphertext: Data, + senderId: String, + recipientId: String, + serverPublicKey: String + ) -> Crypto.Generator<(plaintext: Data, senderSessionIdHex: String)> { + return Crypto.Generator( + id: "plaintextWithSessionBlindingProtocol", + args: [ciphertext, senderId, recipientId] + ) { dependencies in + var cCiphertext: [UInt8] = Array(ciphertext) + var cEd25519SecretKey: [UInt8] = dependencies[cache: .general].ed25519SecretKey + var cSenderId: [UInt8] = Array(Data(hex: senderId)) + var cRecipientId: [UInt8] = Array(Data(hex: recipientId)) + var cServerPublicKey: [UInt8] = Array(Data(hex: serverPublicKey)) + var cSenderSessionId: [CChar] = [CChar](repeating: 0, count: 67) + var maybePlaintext: UnsafeMutablePointer? = nil + var plaintextLen: Int = 0 + + guard !cEd25519SecretKey.isEmpty else { throw MessageSenderError.noUserED25519KeyPair } + guard + cEd25519SecretKey.count == 64, + cServerPublicKey.count == 32, + session_decrypt_for_blinded_recipient( + &cCiphertext, + cCiphertext.count, + &cEd25519SecretKey, + &cServerPublicKey, + &cSenderId, + &cRecipientId, + &cSenderSessionId, + &maybePlaintext, + &plaintextLen + ), + plaintextLen > 0, + let plaintext: Data = maybePlaintext.map({ Data(bytes: $0, count: plaintextLen) }) + else { throw MessageReceiverError.decryptionFailed } + + free(UnsafeMutableRawPointer(mutating: maybePlaintext)) + + return (plaintext, String(cString: cSenderSessionId)) + } + } +} diff --git a/SessionMessagingKit/Open Groups/Models/Capabilities.swift b/SessionMessagingKit/Open Groups/Models/Capabilities.swift deleted file mode 100644 index 2947214b80..0000000000 --- a/SessionMessagingKit/Open Groups/Models/Capabilities.swift +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -extension OpenGroupAPI { - public struct Capabilities: Codable, Equatable { - public let capabilities: [Capability.Variant] - public let missing: [Capability.Variant]? - - // MARK: - Initialization - - public init(capabilities: [Capability.Variant], missing: [Capability.Variant]? = nil) { - self.capabilities = capabilities - self.missing = missing - } - } -} diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 6336a3efd4..e0b7269ffd 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Singleton @@ -35,7 +35,7 @@ public extension Log.Category { // MARK: - OpenGroupManager public final class OpenGroupManager { - public typealias DefaultRoomInfo = (room: OpenGroupAPI.Room, openGroup: OpenGroup) + public typealias DefaultRoomInfo = (room: Network.SOGS.Room, openGroup: OpenGroup) private let dependencies: Dependencies @@ -79,8 +79,8 @@ public final class OpenGroupManager { .replacingOccurrences(of: serverPort, with: "") ) let options: Set = Set([ - OpenGroupAPI.legacyDefaultServerIP, - OpenGroupAPI.defaultServer + Network.SOGS.legacyDefaultServerIP, + Network.SOGS.defaultServer .replacingOccurrences(of: "http://", with: "") .replacingOccurrences(of: "https://", with: "") ]) @@ -103,7 +103,7 @@ public final class OpenGroupManager { .lowercased() .replacingOccurrences(of: serverPort, with: "") ) - let defaultServerHost: String = OpenGroupAPI.defaultServer + let defaultServerHost: String = Network.SOGS.defaultServer .replacingOccurrences(of: "http://", with: "") .replacingOccurrences(of: "https://", with: "") var serverOptions: Set = Set([ @@ -119,9 +119,9 @@ public final class OpenGroupManager { serverOptions.insert(defaultServerHost) serverOptions.insert("http://\(defaultServerHost)") serverOptions.insert("https://\(defaultServerHost)") - serverOptions.insert(OpenGroupAPI.legacyDefaultServerIP) - serverOptions.insert("http://\(OpenGroupAPI.legacyDefaultServerIP)") - serverOptions.insert("https://\(OpenGroupAPI.legacyDefaultServerIP)") + serverOptions.insert(Network.SOGS.legacyDefaultServerIP) + serverOptions.insert("http://\(Network.SOGS.legacyDefaultServerIP)") + serverOptions.insert("https://\(Network.SOGS.legacyDefaultServerIP)") } // First check if there is no poller for the specified server @@ -161,7 +161,7 @@ public final class OpenGroupManager { return server.lowercased() } - return OpenGroupAPI.defaultServer + return Network.SOGS.defaultServer }() let threadId: String = OpenGroup.idFor(roomToken: roomToken, server: targetServer) @@ -223,11 +223,11 @@ public final class OpenGroupManager { return server.lowercased() } - return OpenGroupAPI.defaultServer + return Network.SOGS.defaultServer }() return Result { - try OpenGroupAPI + try Network.SOGS .preparedCapabilitiesAndRoom( roomToken: roomToken, authMethod: Authentication.community( @@ -243,7 +243,7 @@ public final class OpenGroupManager { } .publisher .flatMap { [dependencies] in $0.send(using: dependencies) } - .flatMapStorageWritePublisher(using: dependencies) { [dependencies] (db: ObservingDatabase, response: (info: ResponseInfoType, value: OpenGroupAPI.CapabilitiesAndRoomResponse)) -> Void in + .flatMapStorageWritePublisher(using: dependencies) { [dependencies] (db: ObservingDatabase, response: (info: ResponseInfoType, value: Network.SOGS.CapabilitiesAndRoomResponse)) -> Void in // Add the new open group to libSession try LibSession.add( db, @@ -263,7 +263,7 @@ public final class OpenGroupManager { // Then the room try OpenGroupManager.handlePollInfo( db, - pollInfo: OpenGroupAPI.RoomPollInfo(room: response.value.room.data), + pollInfo: Network.SOGS.RoomPollInfo(room: response.value.room.data), publicKey: publicKey, for: roomToken, on: targetServer, @@ -334,7 +334,7 @@ public final class OpenGroupManager { try MessageDeduplication.deleteIfNeeded(db, threadIds: [openGroupId], using: dependencies) // Remove the open group (no foreign key to the thread so it won't auto-delete) - if server?.lowercased() != OpenGroupAPI.defaultServer.lowercased() { + if server?.lowercased() != Network.SOGS.defaultServer.lowercased() { _ = try? OpenGroup .filter(id: openGroupId) .deleteAll(db) @@ -359,7 +359,7 @@ public final class OpenGroupManager { internal static func handleCapabilities( _ db: ObservingDatabase, - capabilities: OpenGroupAPI.Capabilities, + capabilities: Network.SOGS.CapabilitiesResponse, on server: String ) { // Remove old capabilities first @@ -371,7 +371,7 @@ public final class OpenGroupManager { capabilities.capabilities.forEach { capability in try? Capability( openGroupServer: server.lowercased(), - variant: capability, + variant: Capability.Variant(from: capability), isMissing: false ) .upsert(db) @@ -379,7 +379,7 @@ public final class OpenGroupManager { capabilities.missing?.forEach { capability in try? Capability( openGroupServer: server.lowercased(), - variant: capability, + variant: Capability.Variant(from: capability), isMissing: true ) .upsert(db) @@ -388,7 +388,7 @@ public final class OpenGroupManager { internal static func handlePollInfo( _ db: ObservingDatabase, - pollInfo: OpenGroupAPI.RoomPollInfo, + pollInfo: Network.SOGS.RoomPollInfo, publicKey maybePublicKey: String?, for roomToken: String, on server: String, @@ -431,7 +431,7 @@ public final class OpenGroupManager { .updateAllAndConfig(db, changes, using: dependencies) // Update the admin/moderator group members - if let roomDetails: OpenGroupAPI.Room = pollInfo.details { + if let roomDetails: Network.SOGS.Room = pollInfo.details { _ = try? GroupMember .filter(GroupMember.Columns.groupId == threadId) .deleteAll(db) @@ -531,7 +531,7 @@ public final class OpenGroupManager { internal static func handleMessages( _ db: ObservingDatabase, - messages: [OpenGroupAPI.Message], + messages: [Network.SOGS.Message], for roomToken: String, on server: String, using dependencies: Dependencies @@ -543,7 +543,7 @@ public final class OpenGroupManager { // Sorting the messages by server ID before importing them fixes an issue where messages // that quote older messages can't find those older messages - let sortedMessages: [OpenGroupAPI.Message] = messages + let sortedMessages: [Network.SOGS.Message] = messages .filter { $0.deleted != true } .sorted { lhs, rhs in lhs.id < rhs.id } var messageServerInfoToRemove: [(id: Int64, seqNo: Int64)] = messages @@ -684,7 +684,7 @@ public final class OpenGroupManager { internal static func handleDirectMessages( _ db: ObservingDatabase, - messages: [OpenGroupAPI.DirectMessage], + messages: [Network.SOGS.DirectMessage], fromOutbox: Bool, on server: String, using dependencies: Dependencies @@ -698,7 +698,7 @@ public final class OpenGroupManager { // Sorting the messages by server ID before importing them fixes an issue where messages // that quote older messages can't find those older messages - let sortedMessages: [OpenGroupAPI.DirectMessage] = messages + let sortedMessages: [Network.SOGS.DirectMessage] = messages .sorted { lhs, rhs in lhs.id < rhs.id } let latestMessageId: Int64 = sortedMessages[sortedMessages.count - 1].id var lookupCache: [String: BlindedIdLookup] = [:] // Only want this cache to exist for the current loop @@ -829,9 +829,9 @@ public final class OpenGroupManager { id: Int64, in roomToken: String, on server: String, - type: OpenGroupAPI.PendingChange.ReactAction - ) -> OpenGroupAPI.PendingChange { - let pendingChange = OpenGroupAPI.PendingChange( + type: OpenGroupManager.PendingChange.ReactAction + ) -> OpenGroupManager.PendingChange { + let pendingChange = OpenGroupManager.PendingChange( server: server, room: roomToken, changeType: .reaction, @@ -849,7 +849,7 @@ public final class OpenGroupManager { return pendingChange } - public func updatePendingChange(_ pendingChange: OpenGroupAPI.PendingChange, seqNo: Int64?) { + public func updatePendingChange(_ pendingChange: OpenGroupManager.PendingChange, seqNo: Int64?) { dependencies.mutate(cache: .openGroupManager) { if let index = $0.pendingChanges.firstIndex(of: pendingChange) { $0.pendingChanges[index].seqNo = seqNo @@ -857,7 +857,7 @@ public final class OpenGroupManager { } } - public func removePendingChange(_ pendingChange: OpenGroupAPI.PendingChange) { + public func removePendingChange(_ pendingChange: OpenGroupManager.PendingChange) { dependencies.mutate(cache: .openGroupManager) { if let index = $0.pendingChanges.firstIndex(of: pendingChange) { $0.pendingChanges.remove(at: index) @@ -989,7 +989,7 @@ public extension OpenGroupManager { private let dependencies: Dependencies private let defaultRoomsSubject: CurrentValueSubject<[DefaultRoomInfo], Error> = CurrentValueSubject([]) private var _lastSuccessfulCommunityPollTimestamp: TimeInterval? - public var pendingChanges: [OpenGroupAPI.PendingChange] = [] + public var pendingChanges: [OpenGroupManager.PendingChange] = [] public var defaultRoomsPublisher: AnyPublisher<[DefaultRoomInfo], Error> { defaultRoomsSubject @@ -1044,13 +1044,13 @@ public extension OpenGroupManager { public protocol OGMImmutableCacheType: ImmutableCacheType { var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> { get } - var pendingChanges: [OpenGroupAPI.PendingChange] { get } + var pendingChanges: [OpenGroupManager.PendingChange] { get } } public protocol OGMCacheType: OGMImmutableCacheType, MutableCacheType { var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> { get } - var pendingChanges: [OpenGroupAPI.PendingChange] { get set } + var pendingChanges: [OpenGroupManager.PendingChange] { get set } func getLastSuccessfulCommunityPollTimestamp() -> TimeInterval func setLastSuccessfulCommunityPollTimestamp(_ timestamp: TimeInterval) diff --git a/SessionMessagingKit/Open Groups/Models/PendingChange.swift b/SessionMessagingKit/Open Groups/Types/PendingChange.swift similarity index 67% rename from SessionMessagingKit/Open Groups/Models/PendingChange.swift rename to SessionMessagingKit/Open Groups/Types/PendingChange.swift index dd5af98b5f..6ab6cd2145 100644 --- a/SessionMessagingKit/Open Groups/Models/PendingChange.swift +++ b/SessionMessagingKit/Open Groups/Types/PendingChange.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension OpenGroupManager { public struct PendingChange: Equatable { public enum ChangeType { case reaction @@ -24,23 +24,23 @@ extension OpenGroupAPI { var seqNo: Int64? let metadata: Metadata - public static func == (lhs: OpenGroupAPI.PendingChange, rhs: OpenGroupAPI.PendingChange) -> Bool { - guard lhs.server == rhs.server && - lhs.room == rhs.room && - lhs.changeType == rhs.changeType && - lhs.seqNo == rhs.seqNo - else { - return false - } + public static func == (lhs: OpenGroupManager.PendingChange, rhs: OpenGroupManager.PendingChange) -> Bool { + guard + lhs.server == rhs.server && + lhs.room == rhs.room && + lhs.changeType == rhs.changeType && + lhs.seqNo == rhs.seqNo + else { return false } switch lhs.changeType { case .reaction: if case .reaction(let lhsMessageId, let lhsEmoji, let lhsAction) = lhs.metadata, - case .reaction(let rhsMessageId, let rhsEmoji, let rhsAction) = rhs.metadata { + case .reaction(let rhsMessageId, let rhsEmoji, let rhsAction) = rhs.metadata + { return lhsMessageId == rhsMessageId && lhsEmoji == rhsEmoji && lhsAction == rhsAction - } else { - return false } + + return false } } } diff --git a/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift b/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift index 532c5fe5ec..c543b0dd4b 100644 --- a/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift +++ b/SessionMessagingKit/Sending & Receiving/AttachmentUploader.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - AttachmentUploader @@ -11,7 +11,7 @@ import SessionUtilitiesKit public final class AttachmentUploader { private enum Destination { case fileServer - case community(LibSession.OpenGroupCapabilityInfo) + case community(roomToken: String, server: String) var shouldEncrypt: Bool { switch self { @@ -78,7 +78,9 @@ public final class AttachmentUploader { // Generate the correct upload info based on the state of the attachment let destination: AttachmentUploader.Destination = { switch authMethod { - case let auth as Authentication.community: return .community(auth.openGroupCapabilityInfo) + case let auth as Authentication.community: + return .community(roomToken: auth.roomToken, server: auth.server) + default: return .fileServer } }() @@ -86,14 +88,14 @@ public final class AttachmentUploader { let endpoint: (any EndpointType) = { switch destination { case .fileServer: return Network.FileServer.Endpoint.file - case .community(let info): return OpenGroupAPI.Endpoint.roomFile(info.roomToken) + case .community(let roomToken, _): return Network.SOGS.Endpoint.roomFile(roomToken) } }() // This can occur if an AttachmentUploadJob was explicitly created for a message // dependant on the attachment being uploaded (in this case the attachment has // already been uploaded so just succeed) - if attachment.state == .uploaded, let fileId: String = Attachment.fileId(for: attachment.downloadUrl) { + if attachment.state == .uploaded, let fileId: String = Network.FileServer.fileId(for: attachment.downloadUrl) { return ( attachment, try Network.PreparedRequest.cached( @@ -114,7 +116,7 @@ public final class AttachmentUploader { // Note: The most common cases for this will be for LinkPreviews or Quotes if attachment.state == .downloaded, - let fileId: String = Attachment.fileId(for: attachment.downloadUrl), + let fileId: String = Network.FileServer.fileId(for: attachment.downloadUrl), ( !destination.shouldEncrypt || ( attachment.encryptionKey != nil && @@ -174,13 +176,13 @@ public final class AttachmentUploader { digest ) - case .community(let info): + case .community(let roomToken, _): return ( attachment, - try OpenGroupAPI.preparedUpload( + try Network.SOGS.preparedUpload( data: finalData, - roomToken: info.roomToken, - authMethod: Authentication.community(info: info), + roomToken: roomToken, + authMethod: authMethod, using: dependencies ), encryptionKey, @@ -211,11 +213,11 @@ public final class AttachmentUploader { case (_, _, .fileServer): return Network.FileServer.downloadUrlString(for: response.id) - case (_, _, .community(let info)): - return OpenGroupAPI.downloadUrlString( + case (_, _, .community(let roomToken, let server)): + return Network.SOGS.downloadUrlString( for: response.id, - server: info.server, - roomToken: info.roomToken + server: server, + roomToken: roomToken ) } }(), diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift index af8255fbce..15d5488927 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift @@ -26,13 +26,14 @@ public enum MessageReceiverError: Error, CustomStringConvertible { case duplicatedCall case missingRequiredAdminPrivileges case deprecatedMessage + case failedToProcess public var isRetryable: Bool { switch self { case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .invalidSignature, .noData, .senderBlocked, .noThread, .selfSend, .decryptionFailed, .invalidConfigMessageHandling, .outdatedMessage, .ignorableMessage, .ignorableMessageRequestMessage, - .missingRequiredAdminPrivileges: + .missingRequiredAdminPrivileges, .failedToProcess: return false default: return true @@ -112,6 +113,7 @@ public enum MessageReceiverError: Error, CustomStringConvertible { case .duplicatedCall: return "Duplicate call." case .missingRequiredAdminPrivileges: return "Handling this message requires admin privileges which the current user does not have." case .deprecatedMessage: return "This message type has been deprecated." + case .failedToProcess: return "Failed to process." } } } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift index 9670210fdc..cdd2921bcf 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift @@ -5,7 +5,7 @@ import AVFAudio import GRDB import WebRTC import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift index 4186c65e0b..4c5f597204 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+DataExtractionNotification.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageReceiver { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift index 6ebcee8041..79ca54ca61 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Groups.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageReceiver { @@ -307,7 +307,7 @@ extension MessageReceiver { // devices that had the group before they were promoted try SnodeReceivedMessageInfo .filter(SnodeReceivedMessageInfo.Columns.swarmPublicKey == groupSessionId.hexString) - .filter(SnodeReceivedMessageInfo.Columns.namespace == SnodeAPI.Namespace.groupMessages.rawValue) + .filter(SnodeReceivedMessageInfo.Columns.namespace == Network.SnodeAPI.Namespace.groupMessages.rawValue) .updateAllAndConfig( db, SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid.set(to: true), @@ -753,7 +753,7 @@ extension MessageReceiver { ) else { return } - try? SnodeAPI + try? Network.SnodeAPI .preparedDeleteMessages( serverHashes: Array(hashes), requireSuccessfulDeletion: false, @@ -921,7 +921,7 @@ extension MessageReceiver { db.afterCommit { dependencies[singleton: .storage] .readPublisher { db in - try SnodeAPI.preparedDeleteMessages( + try Network.SnodeAPI.preparedDeleteMessages( serverHashes: [serverHash], requireSuccessfulDeletion: false, authMethod: try Authentication.with( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift index 8396652b28..ec2ecadc46 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit extension MessageReceiver { internal static func handleNewLegacyClosedGroup( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LibSession.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LibSession.swift index acfe478541..f209de30ed 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LibSession.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LibSession.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageReceiver { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift index b5d2893a4d..1ad39c5a05 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+MessageRequests.swift @@ -6,7 +6,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit extension MessageReceiver { internal static func handleMessageRequestResponse( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift index bbb89f2e66..c10460d2f4 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageReceiver { @@ -70,7 +70,7 @@ extension MessageReceiver { case .contact: dependencies[singleton: .storage] .readPublisher { db in - try SnodeAPI.preparedDeleteMessages( + try Network.SnodeAPI.preparedDeleteMessages( serverHashes: Array(hashes), requireSuccessfulDeletion: false, authMethod: try Authentication.with( diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift index c67f091f60..a5ddfb44bf 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageReceiver { diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift index 60e9665c93..8defb0e98d 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit extension MessageSender { private typealias PreparedGroupData = ( @@ -13,7 +13,7 @@ extension MessageSender { thread: SessionThread, group: ClosedGroup, members: [GroupMember], - preparedNotificationsSubscription: Network.PreparedRequest? + preparedNotificationsSubscription: Network.PreparedRequest? ) public static func createGroup( @@ -119,14 +119,19 @@ extension MessageSender { ) // Prepare the notification subscription - var preparedNotificationSubscription: Network.PreparedRequest? + var preparedNotificationSubscription: Network.PreparedRequest? if let token: String = dependencies[defaults: .standard, key: .deviceToken] { - preparedNotificationSubscription = try? PushNotificationAPI + preparedNotificationSubscription = try? Network.PushNotification .preparedSubscribe( - db, token: Data(hex: token), - sessionIds: [createdInfo.groupSessionId], + swarms: [( + createdInfo.groupSessionId, + Authentication.groupAdmin( + groupSessionId: createdInfo.groupSessionId, + ed25519SecretKey: createdInfo.identityKeyPair.secretKey + ) + )], using: dependencies ) } @@ -593,7 +598,7 @@ extension MessageSender { using: dependencies ) - maybeSupplementalKeyRequest = try SnodeAPI.preparedSendMessage( + maybeSupplementalKeyRequest = try Network.SnodeAPI.preparedSendMessage( message: SnodeMessage( recipient: sessionId.hexString, data: supplementData, @@ -677,7 +682,7 @@ extension MessageSender { /// Unrevoke the newly added members just in case they had previously gotten their access to the group /// revoked (fire-and-forget this request, we don't want it to be blocking - if the invited user still can't access /// the group the admin can resend their invitation which will also attempt to unrevoke their subaccount) - let unrevokeRequest: Network.PreparedRequest = try SnodeAPI.preparedUnrevokeSubaccounts( + let unrevokeRequest: Network.PreparedRequest = try Network.SnodeAPI.preparedUnrevokeSubaccounts( subaccountsToUnrevoke: memberJobData.map { _, _, _, subaccountToken in subaccountToken }, authMethod: Authentication.groupAdmin( groupSessionId: sessionId, @@ -856,7 +861,7 @@ extension MessageSender { using: dependencies ) - maybeSupplementalKeyRequest = try SnodeAPI.preparedSendMessage( + maybeSupplementalKeyRequest = try Network.SnodeAPI.preparedSendMessage( message: SnodeMessage( recipient: sessionId.hexString, data: supplementData, @@ -902,7 +907,7 @@ extension MessageSender { /// Unrevoke the member just in case they had previously gotten their access to the group revoked and the /// unrevoke request when initially added them failed (fire-and-forget this request, we don't want it to be blocking) - let unrevokeRequest: Network.PreparedRequest = try SnodeAPI + let unrevokeRequest: Network.PreparedRequest = try Network.SnodeAPI .preparedUnrevokeSubaccounts( subaccountsToUnrevoke: memberInfo.map { token, _ in token }, authMethod: Authentication.groupAdmin( diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index fa3d97aa08..91ce5e83db 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -4,7 +4,7 @@ import Foundation import GRDB import SessionUIKit import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Log.Category diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift index f39f39ee2f..f10e1dca4c 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit extension MessageSender { diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 9c89f145c4..78ed522564 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -3,7 +3,7 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category @@ -43,7 +43,7 @@ public final class MessageSender { public static func preparedSend( message: Message, to destination: Message.Destination, - namespace: SnodeAPI.Namespace?, + namespace: Network.SnodeAPI.Namespace?, interactionId: Int64?, attachments: [(attachment: Attachment, fileId: String)]?, authMethod: AuthenticationMethod, @@ -138,7 +138,7 @@ public final class MessageSender { private static func preparedSendToSnodeDestination( message: Message, to destination: Message.Destination, - namespace: SnodeAPI.Namespace?, + namespace: Network.SnodeAPI.Namespace?, interactionId: Int64?, attachments: [(attachment: Attachment, fileId: String)]?, messageSendTimestampMs: Int64, @@ -146,7 +146,9 @@ public final class MessageSender { onEvent: ((Event) -> Void)?, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - guard let namespace: SnodeAPI.Namespace = namespace else { throw MessageSenderError.invalidMessage } + guard let namespace: Network.SnodeAPI.Namespace = namespace else { + throw MessageSenderError.invalidMessage + } /// Set the sender/recipient info (needed to be valid) /// @@ -201,7 +203,7 @@ public final class MessageSender { // Perform any pre-send actions onEvent?(.willSend(message, destination, interactionId: interactionId)) - return try SnodeAPI + return try Network.SnodeAPI .preparedSendMessage( message: snodeMessage, in: namespace, @@ -287,7 +289,7 @@ public final class MessageSender { // Perform any pre-send actions onEvent?(.willSend(message, destination, interactionId: interactionId)) - return try OpenGroupAPI + return try Network.SOGS .preparedSend( plaintext: plaintext, roomToken: roomToken, @@ -352,7 +354,7 @@ public final class MessageSender { // Perform any pre-send actions onEvent?(.willSend(message, destination, interactionId: interactionId)) - return try OpenGroupAPI + return try Network.SOGS .preparedSend( ciphertext: ciphertext, toInboxFor: recipientBlindedPublicKey, @@ -371,7 +373,7 @@ public final class MessageSender { // MARK: - Message Wrapping public static func encodeMessageForSending( - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, destination: Message.Destination, message: Message, attachments: [(attachment: Attachment, fileId: String)]?, diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupOnlyRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupOnlyRequest.swift deleted file mode 100644 index 1a87dcf8e5..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupOnlyRequest.swift +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -extension PushNotificationAPI { - struct LegacyGroupOnlyRequest: Codable { - let token: String - let pubKey: String - let device: String - let legacyGroupPublicKeys: Set - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupRequest.swift deleted file mode 100644 index 962011dfd4..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyGroupRequest.swift +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -extension PushNotificationAPI { - struct LegacyGroupRequest: Codable { - let pubKey: String - let closedGroupPublicKey: String - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyNotifyRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyNotifyRequest.swift deleted file mode 100644 index 491fa77570..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyNotifyRequest.swift +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -extension PushNotificationAPI { - struct LegacyNotifyRequest: Codable { - enum CodingKeys: String, CodingKey { - case data - case sendTo = "send_to" - } - - let data: String - let sendTo: String - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyPushServerResponse.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyPushServerResponse.swift deleted file mode 100644 index bd412f24e7..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyPushServerResponse.swift +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -public extension PushNotificationAPI { - struct LegacyPushServerResponse: Codable { - let code: Int - let message: String? - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift b/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift deleted file mode 100644 index 663bafb174..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/LegacyUnsubscribeRequest.swift +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import SessionSnodeKit - -extension PushNotificationAPI { - struct LegacyUnsubscribeRequest: Codable { - private let token: String - - init(token: String) { - self.token = token - } - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI+SessionMessagingKit.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI+SessionMessagingKit.swift new file mode 100644 index 0000000000..dda9ad50dd --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI+SessionMessagingKit.swift @@ -0,0 +1,127 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Combine +import GRDB +import SessionNetworkingKit +import SessionUtilitiesKit + +public extension Network.PushNotification { + static func subscribeAll( + token: Data, + isForcedUpdate: Bool, + using dependencies: Dependencies + ) -> AnyPublisher { + let hexEncodedToken: String = token.toHexString() + let oldToken: String? = dependencies[defaults: .standard, key: .deviceToken] + let lastUploadTime: Double = dependencies[defaults: .standard, key: .lastDeviceTokenUpload] + let now: TimeInterval = dependencies.dateNow.timeIntervalSince1970 + + guard isForcedUpdate || hexEncodedToken != oldToken || now - lastUploadTime > tokenExpirationInterval else { + Log.info(.pushNotificationAPI, "Device token hasn't changed or expired; no need to re-upload.") + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + return dependencies[singleton: .storage] + .readPublisher { db -> Network.PreparedRequest in + let userSessionId: SessionId = dependencies[cache: .general].sessionId + let userAuthMethod: AuthenticationMethod = try Authentication.with( + db, + swarmPublicKey: userSessionId.hexString, + using: dependencies + ) + + return try Network.PushNotification + .preparedSubscribe( + token: token, + swarms: [(userSessionId, userAuthMethod)] + .appending(contentsOf: try ClosedGroup + .select(.threadId) + .filter( + ClosedGroup.Columns.threadId > SessionId.Prefix.group.rawValue && + ClosedGroup.Columns.threadId < SessionId.Prefix.group.endOfRangeString + ) + .filter(ClosedGroup.Columns.shouldPoll) + .asRequest(of: String.self) + .fetchSet(db) + .map { threadId in + ( + SessionId(.group, hex: threadId), + try Authentication.with( + db, + swarmPublicKey: threadId, + using: dependencies + ) + ) + } + ), + using: dependencies + ) + .handleEvents( + receiveOutput: { _, response in + guard response.subResponses.first?.success == true else { return } + + dependencies[defaults: .standard, key: .deviceToken] = hexEncodedToken + dependencies[defaults: .standard, key: .lastDeviceTokenUpload] = now + dependencies[defaults: .standard, key: .isUsingFullAPNs] = true + } + ) + } + .flatMap { $0.send(using: dependencies) } + .map { _ in () } + .eraseToAnyPublisher() + } + + public static func unsubscribeAll( + token: Data, + using dependencies: Dependencies + ) -> AnyPublisher { + return dependencies[singleton: .storage] + .readPublisher { db -> Network.PreparedRequest in + let userSessionId: SessionId = dependencies[cache: .general].sessionId + let userAuthMethod: AuthenticationMethod = try Authentication.with( + db, + swarmPublicKey: userSessionId.hexString, + using: dependencies + ) + + return try Network.PushNotification + .preparedUnsubscribe( + token: token, + swarms: [(userSessionId, userAuthMethod)] + .appending(contentsOf: (try? ClosedGroup + .select(.threadId) + .filter( + ClosedGroup.Columns.threadId > SessionId.Prefix.group.rawValue && + ClosedGroup.Columns.threadId < SessionId.Prefix.group.endOfRangeString + ) + .asRequest(of: String.self) + .fetchSet(db)) + .defaulting(to: []) + .map { threadId in + ( + SessionId(.group, hex: threadId), + try Authentication.with( + db, + swarmPublicKey: threadId, + using: dependencies + ) + ) + }), + using: dependencies + ) + .handleEvents( + receiveOutput: { _, response in + guard response.subResponses.first?.success == true else { return } + + dependencies[defaults: .standard, key: .deviceToken] = nil + } + ) + } + .flatMap { $0.send(using: dependencies) } + .map { _ in () } + .eraseToAnyPublisher() + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift deleted file mode 100644 index 5c4bcd9c7c..0000000000 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ /dev/null @@ -1,320 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -// -// stringlint:disable - -import Foundation -import Combine -import GRDB -import SessionSnodeKit -import SessionUtilitiesKit - -// MARK: - KeychainStorage - -public extension KeychainStorage.DataKey { static let pushNotificationEncryptionKey: Self = "PNEncryptionKeyKey" } - -// MARK: - Log.Category - -private extension Log.Category { - static let cat: Log.Category = .create("PushNotificationAPI", defaultLevel: .info) -} - -// MARK: - PushNotificationAPI - -public enum PushNotificationAPI { - internal static let encryptionKeyLength: Int = 32 - private static let maxRetryCount: Int = 4 - private static let tokenExpirationInterval: TimeInterval = (12 * 60 * 60) - - public static let server: String = "https://push.getsession.org" - public static let serverPublicKey = "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b" - - // MARK: - Batch Requests - - public static func subscribeAll( - token: Data, - isForcedUpdate: Bool, - using dependencies: Dependencies - ) -> AnyPublisher { - let hexEncodedToken: String = token.toHexString() - let oldToken: String? = dependencies[defaults: .standard, key: .deviceToken] - let lastUploadTime: Double = dependencies[defaults: .standard, key: .lastDeviceTokenUpload] - let now: TimeInterval = dependencies.dateNow.timeIntervalSince1970 - - guard isForcedUpdate || hexEncodedToken != oldToken || now - lastUploadTime > tokenExpirationInterval else { - Log.info(.cat, "Device token hasn't changed or expired; no need to re-upload.") - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - return dependencies[singleton: .storage] - .readPublisher { db -> Network.PreparedRequest in - let userSessionId: SessionId = dependencies[cache: .general].sessionId - - return try PushNotificationAPI - .preparedSubscribe( - db, - token: token, - sessionIds: [userSessionId] - .appending(contentsOf: try ClosedGroup - .select(.threadId) - .filter( - ClosedGroup.Columns.threadId > SessionId.Prefix.group.rawValue && - ClosedGroup.Columns.threadId < SessionId.Prefix.group.endOfRangeString - ) - .filter(ClosedGroup.Columns.shouldPoll) - .asRequest(of: String.self) - .fetchSet(db) - .map { SessionId(.group, hex: $0) } - ), - using: dependencies - ) - .handleEvents( - receiveOutput: { _, response in - guard response.subResponses.first?.success == true else { return } - - dependencies[defaults: .standard, key: .deviceToken] = hexEncodedToken - dependencies[defaults: .standard, key: .lastDeviceTokenUpload] = now - dependencies[defaults: .standard, key: .isUsingFullAPNs] = true - } - ) - } - .flatMap { $0.send(using: dependencies) } - .map { _ in () } - .eraseToAnyPublisher() - } - - public static func unsubscribeAll( - token: Data, - using dependencies: Dependencies - ) -> AnyPublisher { - return dependencies[singleton: .storage] - .readPublisher { db -> Network.PreparedRequest in - let userSessionId: SessionId = dependencies[cache: .general].sessionId - - return try PushNotificationAPI - .preparedUnsubscribe( - db, - token: token, - sessionIds: [userSessionId] - .appending(contentsOf: (try? ClosedGroup - .select(.threadId) - .filter( - ClosedGroup.Columns.threadId > SessionId.Prefix.group.rawValue && - ClosedGroup.Columns.threadId < SessionId.Prefix.group.endOfRangeString - ) - .asRequest(of: String.self) - .fetchSet(db)) - .defaulting(to: []) - .map { SessionId(.group, hex: $0) }), - using: dependencies - ) - .handleEvents( - receiveOutput: { _, response in - guard response.subResponses.first?.success == true else { return } - - dependencies[defaults: .standard, key: .deviceToken] = nil - } - ) - } - .flatMap { $0.send(using: dependencies) } - .map { _ in () } - .eraseToAnyPublisher() - } - - // MARK: - Prepared Requests - - public static func preparedSubscribe( - _ db: ObservingDatabase, - token: Data, - sessionIds: [SessionId], - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - guard dependencies[defaults: .standard, key: .isUsingFullAPNs] else { - throw NetworkError.invalidPreparedRequest - } - - guard let notificationsEncryptionKey: Data = try? dependencies[singleton: .keychain].getOrGenerateEncryptionKey( - forKey: .pushNotificationEncryptionKey, - length: encryptionKeyLength, - cat: .cat, - legacyKey: "PNEncryptionKeyKey", - legacyService: "PNKeyChainService" - ) else { - Log.error(.cat, "Unable to retrieve PN encryption key.") - throw KeychainStorageError.keySpecInvalid - } - - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: Endpoint.subscribe, - body: SubscribeRequest( - subscriptions: sessionIds.map { sessionId -> SubscribeRequest.Subscription in - SubscribeRequest.Subscription( - namespaces: { - switch sessionId.prefix { - case .group: return [ - .groupMessages, - .configGroupKeys, - .configGroupInfo, - .configGroupMembers, - .revokedRetrievableGroupMessages - ] - default: return [ - .default, - .configUserProfile, - .configContacts, - .configConvoInfoVolatile, - .configUserGroups - ] - } - }(), - /// Note: Unfortunately we always need the message content because without the content - /// control messages can't be distinguished from visible messages which results in the - /// 'generic' notification being shown when receiving things like typing indicator updates - includeMessageData: true, - serviceInfo: ServiceInfo( - token: token.toHexString() - ), - notificationsEncryptionKey: notificationsEncryptionKey, - authMethod: try Authentication.with( - db, - swarmPublicKey: sessionId.hexString, - using: dependencies - ), - timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000) // Seconds - ) - } - ) - ), - responseType: SubscribeResponse.self, - retryCount: PushNotificationAPI.maxRetryCount, - using: dependencies - ) - .handleEvents( - receiveOutput: { _, response in - zip(response.subResponses, sessionIds).forEach { subResponse, sessionId in - guard subResponse.success != true else { return } - - Log.error(.cat, "Couldn't subscribe for push notifications for: \(sessionId) due to error (\(subResponse.error ?? -1)): \(subResponse.message ?? "nil").") - } - }, - receiveCompletion: { result in - switch result { - case .finished: break - case .failure(let error): Log.error(.cat, "Couldn't subscribe for push notifications due to error: \(error).") - } - } - ) - } - - public static func preparedUnsubscribe( - _ db: ObservingDatabase, - token: Data, - sessionIds: [SessionId], - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: Request( - method: .post, - endpoint: Endpoint.unsubscribe, - body: UnsubscribeRequest( - subscriptions: sessionIds.map { sessionId -> UnsubscribeRequest.Subscription in - UnsubscribeRequest.Subscription( - serviceInfo: ServiceInfo( - token: token.toHexString() - ), - authMethod: try Authentication.with( - db, - swarmPublicKey: sessionId.hexString, - using: dependencies - ), - timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000) // Seconds - ) - } - ) - ), - responseType: UnsubscribeResponse.self, - retryCount: PushNotificationAPI.maxRetryCount, - using: dependencies - ) - .handleEvents( - receiveOutput: { _, response in - zip(response.subResponses, sessionIds).forEach { subResponse, sessionId in - guard subResponse.success != true else { return } - - Log.error(.cat, "Couldn't unsubscribe for push notifications for: \(sessionId) due to error (\(subResponse.error ?? -1)): \(subResponse.message ?? "nil").") - } - }, - receiveCompletion: { result in - switch result { - case .finished: break - case .failure(let error): Log.error(.cat, "Couldn't unsubscribe for push notifications due to error: \(error).") - } - } - ) - } - - // MARK: - Notification Handling - - public static func processNotification( - notificationContent: UNNotificationContent, - using dependencies: Dependencies - ) -> (data: Data?, metadata: NotificationMetadata, result: ProcessResult) { - // Make sure the notification is from the updated push server - guard notificationContent.userInfo["spns"] != nil else { - return (nil, .invalid, .legacyFailure) - } - - guard let base64EncodedEncString: String = notificationContent.userInfo["enc_payload"] as? String else { - return (nil, .invalid, .failureNoContent) - } - - // Decrypt and decode the payload - let notification: BencodeResponse - - do { - guard let encryptedData: Data = Data(base64Encoded: base64EncodedEncString) else { - throw CryptoError.invalidBase64EncodedData - } - - let notificationsEncryptionKey: Data = try dependencies[singleton: .keychain].getOrGenerateEncryptionKey( - forKey: .pushNotificationEncryptionKey, - length: encryptionKeyLength, - cat: .cat, - legacyKey: "PNEncryptionKeyKey", - legacyService: "PNKeyChainService" - ) - let decryptedData: Data = try dependencies[singleton: .crypto].tryGenerate( - .plaintextWithPushNotificationPayload( - payload: encryptedData, - encKey: notificationsEncryptionKey - ) - ) - notification = try BencodeDecoder(using: dependencies) - .decode(BencodeResponse.self, from: decryptedData) - } - catch { - Log.error(.cat, "Failed to decrypt or decode notification due to error: \(error)") - return (nil, .invalid, .failure) - } - - // If the metadata says that the message was too large then we should show the generic - // notification (this is a valid case) - guard !notification.info.dataTooLong else { return (nil, notification.info, .successTooLong) } - - // Check that the body we were given is valid and not empty - guard - let notificationData: Data = notification.data, - notification.info.dataLength == notificationData.count, - !notificationData.isEmpty - else { - Log.error(.cat, "Get notification data failed") - return (nil, notification.info, .failureNoContent) - } - - // Success, we have the notification content - return (notificationData, notification.info, .success) - } -} diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift index 835329ec9c..40477f59f3 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CommunityPoller.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Cache @@ -20,7 +20,7 @@ public extension Cache { // MARK: - CommunityPollerType public protocol CommunityPollerType { - typealias PollResponse = (info: ResponseInfoType, data: Network.BatchResponseMap) + typealias PollResponse = (info: ResponseInfoType, data: Network.BatchResponseMap) var isPolling: Bool { get } var receivedPollResponse: AnyPublisher { get } @@ -31,14 +31,13 @@ public protocol CommunityPollerType { // MARK: - CommunityPoller -private typealias Capabilities = OpenGroupAPI.Capabilities +private typealias Capabilities = Network.SOGS.CapabilitiesResponse public final class CommunityPoller: CommunityPollerType & PollerType { // MARK: - Settings private static let minPollInterval: TimeInterval = 3 private static let maxPollInterval: TimeInterval = (60 * 60) - internal static let maxInactivityPeriod: TimeInterval = (14 * 24 * 60 * 60) /// If there are hidden rooms that we poll and they fail too many times we want to prune them (as it likely means they no longer /// exist, and since they are already hidden it's unlikely that the user will notice that we stopped polling for them) @@ -75,7 +74,7 @@ public final class CommunityPoller: CommunityPollerType & PollerType { pollerQueue: DispatchQueue, pollerDestination: PollerDestination, pollerDrainBehaviour: ThreadSafeObject = .alwaysRandom, - namespaces: [SnodeAPI.Namespace] = [], + namespaces: [Network.SnodeAPI.Namespace] = [], failureCount: Int, shouldStoreMessages: Bool, logStartAndStopCalls: Bool, @@ -217,13 +216,13 @@ public final class CommunityPoller: CommunityPollerType & PollerType { .subscribe(on: pollerQueue, using: dependencies) .receive(on: pollerQueue, using: dependencies) .tryMap { [dependencies] authMethod in - try OpenGroupAPI.preparedCapabilities( + try Network.SOGS.preparedCapabilities( authMethod: authMethod, using: dependencies ) } .flatMap { [dependencies] in $0.send(using: dependencies) } - .flatMapStorageWritePublisher(using: dependencies) { [pollerDestination] (db: ObservingDatabase, response: (info: ResponseInfoType, data: OpenGroupAPI.Capabilities)) in + .flatMapStorageWritePublisher(using: dependencies) { [pollerDestination] (db: ObservingDatabase, response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesResponse)) in OpenGroupManager.handleCapabilities( db, capabilities: response.data, @@ -260,7 +259,7 @@ public final class CommunityPoller: CommunityPollerType & PollerType { /// for cases where we need explicit/custom behaviours to occur (eg. Onboarding) public func poll(forceSynchronousProcessing: Bool = false) -> AnyPublisher { typealias PollInfo = ( - roomInfo: [OpenGroupAPI.RoomInfo], + roomInfo: [Network.SOGS.PollRoomInfo], lastInboxMessageId: Int64, lastOutboxMessageId: Int64, authMethod: AuthenticationMethod @@ -276,15 +275,15 @@ public final class CommunityPoller: CommunityPollerType & PollerType { .readPublisher { [pollerDestination, dependencies] db -> PollInfo in /// **Note:** The `OpenGroup` type converts to lowercase in init let server: String = pollerDestination.target.lowercased() - let roomInfo: [OpenGroupAPI.RoomInfo] = try OpenGroup + let roomInfo: [Network.SOGS.PollRoomInfo] = try OpenGroup .select(.roomToken, .infoUpdates, .sequenceNumber) .filter(OpenGroup.Columns.server == server) .filter(OpenGroup.Columns.isActive == true) .filter(OpenGroup.Columns.roomToken != "") - .asRequest(of: OpenGroupAPI.RoomInfo.self) + .asRequest(of: Network.SOGS.PollRoomInfo.self) .fetchAll(db) - guard !roomInfo.isEmpty else { throw OpenGroupAPIError.invalidPoll } + guard !roomInfo.isEmpty else { throw SOGSError.invalidPoll } return ( roomInfo, @@ -303,12 +302,15 @@ public final class CommunityPoller: CommunityPollerType & PollerType { try Authentication.with(db, server: server, using: dependencies) ) } - .tryFlatMap { [pollCount, dependencies] pollInfo -> AnyPublisher<(ResponseInfoType, Network.BatchResponseMap), Error> in - try OpenGroupAPI + .tryFlatMap { [pollCount, dependencies] pollInfo -> AnyPublisher<(ResponseInfoType, Network.BatchResponseMap), Error> in + try Network.SOGS .preparedPoll( roomInfo: pollInfo.roomInfo, lastInboxMessageId: pollInfo.lastInboxMessageId, lastOutboxMessageId: pollInfo.lastOutboxMessageId, + checkForCommunityMessageRequests: dependencies.mutate(cache: .libSession) { + $0.get(.checkForCommunityMessageRequests) + }, hasPerformedInitialPoll: (pollCount > 0), timeSinceLastPoll: (dependencies.dateNow.timeIntervalSince1970 - lastSuccessfulPollTimestamp), authMethod: pollInfo.authMethod, @@ -340,16 +342,16 @@ public final class CommunityPoller: CommunityPollerType & PollerType { private func handlePollResponse( info: ResponseInfoType, - response: Network.BatchResponseMap, + response: Network.BatchResponseMap, failureCount: Int, using dependencies: Dependencies ) -> AnyPublisher { var rawMessageCount: Int = 0 - let validResponses: [OpenGroupAPI.Endpoint: Any] = response.data + let validResponses: [Network.SOGS.Endpoint: Any] = response.data .filter { endpoint, data in switch endpoint { case .capabilities: - guard (data as? Network.BatchSubResponse)?.body != nil else { + guard (data as? Network.BatchSubResponse)?.body != nil else { Log.error(.poller, "\(pollerName) failed due to invalid capability data.") return false } @@ -357,8 +359,8 @@ public final class CommunityPoller: CommunityPollerType & PollerType { return true case .roomPollInfo(let roomToken, _): - guard (data as? Network.BatchSubResponse)?.body != nil else { - switch (data as? Network.BatchSubResponse)?.code { + guard (data as? Network.BatchSubResponse)?.body != nil else { + switch (data as? Network.BatchSubResponse)?.code { case 404: Log.error(.poller, "\(pollerName) failed to retrieve info for unknown room '\(roomToken)'.") default: Log.error(.poller, "\(pollerName) failed due to invalid room info data.") } @@ -369,17 +371,17 @@ public final class CommunityPoller: CommunityPollerType & PollerType { case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _): guard - let responseData: Network.BatchSubResponse<[Failable]> = data as? Network.BatchSubResponse<[Failable]>, - let responseBody: [Failable] = responseData.body + let responseData: Network.BatchSubResponse<[Failable]> = data as? Network.BatchSubResponse<[Failable]>, + let responseBody: [Failable] = responseData.body else { - switch (data as? Network.BatchSubResponse<[Failable]>)?.code { + switch (data as? Network.BatchSubResponse<[Failable]>)?.code { case 404: Log.error(.poller, "\(pollerName) failed to retrieve messages for unknown room '\(roomToken)'.") default: Log.error(.poller, "\(pollerName) failed due to invalid messages data.") } return false } - let successfulMessages: [OpenGroupAPI.Message] = responseBody.compactMap { $0.value } + let successfulMessages: [Network.SOGS.Message] = responseBody.compactMap { $0.value } rawMessageCount += successfulMessages.count if successfulMessages.count != responseBody.count { @@ -392,7 +394,7 @@ public final class CommunityPoller: CommunityPollerType & PollerType { case .inbox, .inboxSince, .outbox, .outboxSince: guard - let responseData: Network.BatchSubResponse<[OpenGroupAPI.DirectMessage]?> = data as? Network.BatchSubResponse<[OpenGroupAPI.DirectMessage]?>, + let responseData: Network.BatchSubResponse<[Network.SOGS.DirectMessage]?> = data as? Network.BatchSubResponse<[Network.SOGS.DirectMessage]?>, !responseData.failedToParseBody else { Log.error(.poller, "\(pollerName) failed due to invalid inbox/outbox data.") @@ -400,7 +402,7 @@ public final class CommunityPoller: CommunityPollerType & PollerType { } // Double optional because the server can return a `304` with an empty body - let messages: [OpenGroupAPI.DirectMessage] = ((responseData.body ?? []) ?? []) + let messages: [Network.SOGS.DirectMessage] = ((responseData.body ?? []) ?? []) rawMessageCount += messages.count return !messages.isEmpty @@ -428,18 +430,18 @@ public final class CommunityPoller: CommunityPollerType & PollerType { } return dependencies[singleton: .storage] - .readPublisher { [pollerDestination] db -> (capabilities: OpenGroupAPI.Capabilities, groups: [OpenGroup]) in + .readPublisher { [pollerDestination] db -> (capabilities: Network.SOGS.CapabilitiesResponse, groups: [OpenGroup]) in let allCapabilities: [Capability] = try Capability .filter(Capability.Columns.openGroupServer == pollerDestination.target) .fetchAll(db) - let capabilities: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities( + let capabilities: Network.SOGS.CapabilitiesResponse = Network.SOGS.CapabilitiesResponse( capabilities: allCapabilities .filter { !$0.isMissing } - .map { $0.variant }, + .map { $0.variant.rawValue }, missing: { - let missingCapabilities: [Capability.Variant] = allCapabilities + let missingCapabilities: [String] = allCapabilities .filter { $0.isMissing } - .map { $0.variant } + .map { $0.variant.rawValue } return (missingCapabilities.isEmpty ? nil : missingCapabilities) }() @@ -452,22 +454,22 @@ public final class CommunityPoller: CommunityPollerType & PollerType { return (capabilities, groups) } - .flatMap { [pollerDestination, dependencies] (capabilities: OpenGroupAPI.Capabilities, groups: [OpenGroup]) -> AnyPublisher in - let changedResponses: [OpenGroupAPI.Endpoint: Any] = validResponses + .flatMap { [pollerDestination, dependencies] (capabilities: Network.SOGS.CapabilitiesResponse, groups: [OpenGroup]) -> AnyPublisher in + let changedResponses: [Network.SOGS.Endpoint: Any] = validResponses .filter { endpoint, data in switch endpoint { case .capabilities: guard - let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, - let responseBody: OpenGroupAPI.Capabilities = responseData.body + let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, + let responseBody: Network.SOGS.CapabilitiesResponse = responseData.body else { return false } return (responseBody != capabilities) case .roomPollInfo(let roomToken, _): guard - let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, - let responseBody: OpenGroupAPI.RoomPollInfo = responseData.body + let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, + let responseBody: Network.SOGS.RoomPollInfo = responseData.body else { return false } guard let existingOpenGroup: OpenGroup = groups.first(where: { $0.roomToken == roomToken }) else { return true @@ -507,8 +509,8 @@ public final class CommunityPoller: CommunityPollerType & PollerType { switch endpoint { case .capabilities: guard - let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, - let responseBody: OpenGroupAPI.Capabilities = responseData.body + let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, + let responseBody: Network.SOGS.CapabilitiesResponse = responseData.body else { return } OpenGroupManager.handleCapabilities( @@ -519,8 +521,8 @@ public final class CommunityPoller: CommunityPollerType & PollerType { case .roomPollInfo(let roomToken, _): guard - let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, - let responseBody: OpenGroupAPI.RoomPollInfo = responseData.body + let responseData: Network.BatchSubResponse = data as? Network.BatchSubResponse, + let responseBody: Network.SOGS.RoomPollInfo = responseData.body else { return } try OpenGroupManager.handlePollInfo( @@ -534,8 +536,8 @@ public final class CommunityPoller: CommunityPollerType & PollerType { case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _): guard - let responseData: Network.BatchSubResponse<[Failable]> = data as? Network.BatchSubResponse<[Failable]>, - let responseBody: [Failable] = responseData.body + let responseData: Network.BatchSubResponse<[Failable]> = data as? Network.BatchSubResponse<[Failable]>, + let responseBody: [Failable] = responseData.body else { return } interactionInfo.append( @@ -550,12 +552,12 @@ public final class CommunityPoller: CommunityPollerType & PollerType { case .inbox, .inboxSince, .outbox, .outboxSince: guard - let responseData: Network.BatchSubResponse<[OpenGroupAPI.DirectMessage]?> = data as? Network.BatchSubResponse<[OpenGroupAPI.DirectMessage]?>, + let responseData: Network.BatchSubResponse<[Network.SOGS.DirectMessage]?> = data as? Network.BatchSubResponse<[Network.SOGS.DirectMessage]?>, !responseData.failedToParseBody else { return } // Double optional because the server can return a `304` with an empty body - let messages: [OpenGroupAPI.DirectMessage] = ((responseData.body ?? []) ?? []) + let messages: [Network.SOGS.DirectMessage] = ((responseData.body ?? []) ?? []) let fromOutbox: Bool = { switch endpoint { case .outbox, .outboxSince: return true @@ -734,4 +736,4 @@ public extension CommunityPollerCacheType { // MARK: - Conformance -extension OpenGroupAPI.RoomInfo: FetchableRecord {} +extension Network.SOGS.PollRoomInfo: @retroactive FetchableRecord {} diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift index e9290f1800..57c63b8be8 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Singleton @@ -33,7 +33,7 @@ public extension Singleton { // MARK: - CurrentUserPoller public final class CurrentUserPoller: SwarmPoller { - public static let namespaces: [SnodeAPI.Namespace] = [ + public static let namespaces: [Network.SnodeAPI.Namespace] = [ .default, .configUserProfile, .configContacts, .configConvoInfoVolatile, .configUserGroups ] private let pollInterval: TimeInterval = 1.5 diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift index 667ec2909c..7e816d4a4f 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Cache @@ -23,7 +23,7 @@ public final class GroupPoller: SwarmPoller { private let minPollInterval: Double = 3 private let maxPollInterval: Double = 30 - public static func namespaces(swarmPublicKey: String) -> [SnodeAPI.Namespace] { + public static func namespaces(swarmPublicKey: String) -> [Network.SnodeAPI.Namespace] { guard (try? SessionId.Prefix(from: swarmPublicKey)) == .group else { return [.legacyClosedGroup] } @@ -62,7 +62,7 @@ public final class GroupPoller: SwarmPoller { .flatMap { [receivedPollResponse] _ in receivedPollResponse } .first() .map { $0.filter { $0.isConfigMessage } } - .filter { !$0.contains(where: { $0.namespace == SnodeAPI.Namespace.configGroupKeys }) } + .filter { !$0.contains(where: { $0.namespace == Network.SnodeAPI.Namespace.configGroupKeys }) } .sinkUntilComplete( receiveValue: { [pollerDestination, pollerName, dependencies] configMessages in Log.error(.poller, "\(pollerName) received no config messages in it's first poll, flagging as expired.") diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift b/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift index 8cdacd5c99..c3e16e5fc5 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/PollerType.swift @@ -4,7 +4,7 @@ import Foundation import Combine -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category @@ -64,7 +64,7 @@ public protocol PollerType: AnyObject { pollerQueue: DispatchQueue, pollerDestination: PollerDestination, pollerDrainBehaviour: ThreadSafeObject, - namespaces: [SnodeAPI.Namespace], + namespaces: [Network.SnodeAPI.Namespace], failureCount: Int, shouldStoreMessages: Bool, logStartAndStopCalls: Bool, diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift index b632d6d87d..ab0851838b 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/SwarmPoller.swift @@ -3,7 +3,7 @@ import Foundation import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - SwarmPollerType @@ -41,7 +41,7 @@ public class SwarmPoller: SwarmPollerType & PollerType { public var lastPollStart: TimeInterval = 0 public var cancellable: AnyCancellable? - private let namespaces: [SnodeAPI.Namespace] + private let namespaces: [Network.SnodeAPI.Namespace] private let customAuthMethod: AuthenticationMethod? private let shouldStoreMessages: Bool private let receivedPollResponseSubject: PassthroughSubject = PassthroughSubject() @@ -53,7 +53,7 @@ public class SwarmPoller: SwarmPollerType & PollerType { pollerQueue: DispatchQueue, pollerDestination: PollerDestination, pollerDrainBehaviour: ThreadSafeObject, - namespaces: [SnodeAPI.Namespace], + namespaces: [Network.SnodeAPI.Namespace], failureCount: Int = 0, shouldStoreMessages: Bool, logStartAndStopCalls: Bool, @@ -108,8 +108,8 @@ public class SwarmPoller: SwarmPollerType & PollerType { /// Fetch the messages return dependencies[singleton: .network] .getSwarm(for: pollerDestination.target) - .tryFlatMapWithRandomSnode(drainBehaviour: _pollerDrainBehaviour, using: dependencies) { [pollerDestination, customAuthMethod, namespaces, dependencies] snode -> AnyPublisher<(LibSession.Snode, Network.PreparedRequest), Error> in - dependencies[singleton: .storage].readPublisher { db -> (LibSession.Snode, Network.PreparedRequest) in + .tryFlatMapWithRandomSnode(drainBehaviour: _pollerDrainBehaviour, using: dependencies) { [pollerDestination, customAuthMethod, namespaces, dependencies] snode -> AnyPublisher<(LibSession.Snode, Network.PreparedRequest), Error> in + dependencies[singleton: .storage].readPublisher { db -> (LibSession.Snode, Network.PreparedRequest) in let authMethod: AuthenticationMethod = try (customAuthMethod ?? Authentication.with( db, swarmPublicKey: pollerDestination.target, @@ -118,7 +118,7 @@ public class SwarmPoller: SwarmPollerType & PollerType { return ( snode, - try SnodeAPI.preparedPoll( + try Network.SnodeAPI.preparedPoll( db, namespaces: namespaces, refreshingConfigHashes: activeHashes, @@ -134,10 +134,10 @@ public class SwarmPoller: SwarmPollerType & PollerType { .map { _, response in (snode, response) } } .flatMapStorageWritePublisher(using: dependencies, updates: { [pollerDestination, shouldStoreMessages, forceSynchronousProcessing, dependencies] db, info -> ([Job], [Job], PollResult) in - let (snode, namespacedResults): (LibSession.Snode, SnodeAPI.PollResponse) = info + let (snode, namespacedResults): (LibSession.Snode, Network.SnodeAPI.PollResponse) = info /// Get all of the messages and sort them by their required `processingOrder` - typealias MessageData = (namespace: SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?) + typealias MessageData = (namespace: Network.SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?) let sortedMessages: [MessageData] = namespacedResults .compactMap { namespace, result -> MessageData? in (result.data?.messages).map { (namespace, $0, result.data?.lastHash) } @@ -233,7 +233,7 @@ public class SwarmPoller: SwarmPollerType & PollerType { shouldStoreMessages: Bool, ignoreDedupeFiles: Bool, forceSynchronousProcessing: Bool, - sortedMessages: [(namespace: SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?)], + sortedMessages: [(namespace: Network.SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?)], using dependencies: Dependencies ) -> ([Job], [Job], PollResult) { /// No need to do anything if there are no messages @@ -386,16 +386,17 @@ public class SwarmPoller: SwarmPollerType & PollerType { } } - /// Make sure to add any synchronously processed messages to the `finalProcessedMessages` - /// as otherwise they wouldn't be emitted by the `receivedPollResponseSubject` + /// Make sure to add any synchronously processed messages to the `finalProcessedMessages` as otherwise + /// they wouldn't be emitted by the `receivedPollResponseSubject`, also need to add the count to + /// `messageCount` to ensure it's not incorrect finalProcessedMessages += processedMessages + messageCount += processedMessages.count return nil } .flatMap { $0 } /// If we don't want to store the messages then no need to continue (don't want to create message receive jobs or mess with cached hashes) guard shouldStoreMessages && !forceSynchronousProcessing else { - messageCount += allProcessedMessages.count finalProcessedMessages += allProcessedMessages return ([], [], (finalProcessedMessages, rawMessageCount, messageCount, hadValidHashUpdate)) } diff --git a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift index 3f5da3a998..3486db29e6 100644 --- a/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift +++ b/SessionMessagingKit/Sending & Receiving/Typing Indicators/TypingIndicators.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit // MARK: - Singleton diff --git a/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift b/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift index 9cbad6bf0b..67fd9318a2 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel+DeletionActions.swift @@ -4,7 +4,7 @@ import Foundation import Combine import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public extension MessageViewModel { @@ -124,7 +124,7 @@ public extension MessageViewModel.DeletionBehaviours { enum SelectedMessageState { case outgoingOnly case containsIncoming - case containsDeletedOrControlMessages + case containsLocalOnlyMessages /// Control, pending or deleted messages } /// If it's a legacy group and they have been deprecated then the user shouldn't be able to delete messages @@ -134,8 +134,9 @@ public extension MessageViewModel.DeletionBehaviours { let state: SelectedMessageState = { guard !cellViewModels.contains(where: { $0.variant.isDeletedMessage }) && - !cellViewModels.contains(where: { $0.variant.isInfoMessage }) - else { return .containsDeletedOrControlMessages } + !cellViewModels.contains(where: { $0.variant.isInfoMessage }) && + !cellViewModels.contains(where: { $0.state == .sending || $0.state == .failed }) + else { return .containsLocalOnlyMessages } return (cellViewModels.contains(where: { $0.variant == .standardIncoming }) ? .containsIncoming : @@ -171,8 +172,8 @@ public extension MessageViewModel.DeletionBehaviours { }() switch (state, isAdmin) { - /// User selects messages including a control message or “deleted” message - case (.containsDeletedOrControlMessages, _): + /// User selects messages including a control, pending or “deleted” message + case (.containsLocalOnlyMessages, _): return MessageViewModel.DeletionBehaviours( title: "deleteMessage" .putNumber(cellViewModels.count) @@ -415,7 +416,7 @@ public extension MessageViewModel.DeletionBehaviours { .chunked(by: Network.BatchRequest.childRequestLimit) .map { unsendRequestChunk in .preparedRequest( - try SnodeAPI.preparedBatch( + try Network.SnodeAPI.preparedBatch( requests: unsendRequestChunk, requireAllBatchResponses: false, swarmPublicKey: threadData.threadId, @@ -426,7 +427,7 @@ public extension MessageViewModel.DeletionBehaviours { ) .appending(serverHashes.isEmpty ? nil : .preparedRequest( - try SnodeAPI.preparedDeleteMessages( + try Network.SnodeAPI.preparedDeleteMessages( serverHashes: Array(serverHashes), requireSuccessfulDeletion: false, authMethod: try Authentication.with( @@ -496,7 +497,7 @@ public extension MessageViewModel.DeletionBehaviours { .chunked(by: Network.BatchRequest.childRequestLimit) .map { unsendRequestChunk in .preparedRequest( - try SnodeAPI.preparedBatch( + try Network.SnodeAPI.preparedBatch( requests: unsendRequestChunk, requireAllBatchResponses: false, swarmPublicKey: threadData.threadId, @@ -616,7 +617,7 @@ public extension MessageViewModel.DeletionBehaviours { ) ) .appending(serverHashes.isEmpty ? nil : - .preparedRequest(try SnodeAPI + .preparedRequest(try Network.SnodeAPI .preparedDeleteMessages( serverHashes: Array(serverHashes), requireSuccessfulDeletion: false, @@ -658,7 +659,7 @@ public extension MessageViewModel.DeletionBehaviours { let deleteRequests: [Network.PreparedRequest] = try cellViewModels .compactMap { $0.openGroupServerMessageId } .map { messageId in - try OpenGroupAPI.preparedMessageDelete( + try Network.SOGS.preparedMessageDelete( id: messageId, roomToken: roomToken, authMethod: authMethod, @@ -674,7 +675,7 @@ public extension MessageViewModel.DeletionBehaviours { .chunked(by: Network.BatchRequest.childRequestLimit) .map { deleteRequestsChunk in .preparedRequest( - try OpenGroupAPI.preparedBatch( + try Network.SOGS.preparedBatch( requests: deleteRequestsChunk, authMethod: authMethod, using: dependencies diff --git a/SessionMessagingKit/Utilities/AttachmentManager.swift b/SessionMessagingKit/Utilities/AttachmentManager.swift index 5746315ac1..394cb1fef0 100644 --- a/SessionMessagingKit/Utilities/AttachmentManager.swift +++ b/SessionMessagingKit/Utilities/AttachmentManager.swift @@ -7,7 +7,7 @@ import Combine import UniformTypeIdentifiers import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Singleton diff --git a/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift b/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift index 4a1069c1ca..d51fb9fd78 100644 --- a/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift +++ b/SessionMessagingKit/Utilities/Authentication+SessionMessagingKit.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Authentication Types @@ -78,38 +78,6 @@ public extension Authentication { } } } - - /// Used when interacting with a community - struct community: AuthenticationMethod { - public let openGroupCapabilityInfo: LibSession.OpenGroupCapabilityInfo - public let forceBlinded: Bool - - public var server: String { openGroupCapabilityInfo.server } - public var publicKey: String { openGroupCapabilityInfo.publicKey } - public var hasCapabilities: Bool { !openGroupCapabilityInfo.capabilities.isEmpty } - public var supportsBlinding: Bool { openGroupCapabilityInfo.capabilities.contains(.blind) } - - public var info: Info { - .community( - server: server, - publicKey: publicKey, - hasCapabilities: hasCapabilities, - supportsBlinding: supportsBlinding, - forceBlinded: forceBlinded - ) - } - - public init(info: LibSession.OpenGroupCapabilityInfo, forceBlinded: Bool = false) { - self.openGroupCapabilityInfo = info - self.forceBlinded = forceBlinded - } - - // MARK: - SignatureGenerator - - public func generateSignature(with verificationBytes: [UInt8], using dependencies: Dependencies) throws -> Authentication.Signature { - throw CryptoError.signatureGenerationFailed - } - } } // MARK: - Convenience @@ -119,6 +87,19 @@ fileprivate struct GroupAuthData: Codable, FetchableRecord { let authData: Data? } +public extension Authentication.community { + init(info: LibSession.OpenGroupCapabilityInfo, forceBlinded: Bool = false) { + self.init( + roomToken: info.roomToken, + server: info.server, + publicKey: info.publicKey, + hasCapabilities: !info.capabilities.isEmpty, + supportsBlinding: info.capabilities.contains(.blind), + forceBlinded: forceBlinded + ) + } +} + public extension Authentication { static func with( _ db: ObservingDatabase, diff --git a/SessionMessagingKit/Utilities/DisplayPictureManager.swift b/SessionMessagingKit/Utilities/DisplayPictureManager.swift index ae3b2adf93..6663fcf0f8 100644 --- a/SessionMessagingKit/Utilities/DisplayPictureManager.swift +++ b/SessionMessagingKit/Utilities/DisplayPictureManager.swift @@ -4,7 +4,7 @@ import UIKit import Combine import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Singleton diff --git a/SessionMessagingKit/Utilities/ExtensionHelper.swift b/SessionMessagingKit/Utilities/ExtensionHelper.swift index 0b04520659..0075b91d78 100644 --- a/SessionMessagingKit/Utilities/ExtensionHelper.swift +++ b/SessionMessagingKit/Utilities/ExtensionHelper.swift @@ -1,7 +1,7 @@ // Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Singleton @@ -738,7 +738,7 @@ public class ExtensionHelper: ExtensionHelperType { } public func loadMessages() async throws { - typealias MessageData = (namespace: SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?) + typealias MessageData = (namespace: Network.SnodeAPI.Namespace, messages: [SnodeReceivedMessage], lastHash: String?) /// Retrieve all conversation file paths /// @@ -781,7 +781,7 @@ public class ExtensionHelper: ExtensionHelperType { do { let sortedMessages: [MessageData] = try configMessageHashes - .reduce([SnodeAPI.Namespace: [SnodeReceivedMessage]]()) { (result: [SnodeAPI.Namespace: [SnodeReceivedMessage]], hash: String) in + .reduce([Network.SnodeAPI.Namespace: [SnodeReceivedMessage]]()) { (result: [Network.SnodeAPI.Namespace: [SnodeReceivedMessage]], hash: String) in let path: String = URL(fileURLWithPath: this.conversationsPath) .appendingPathComponent(conversationHash) .appendingPathComponent(this.conversationConfigDir) @@ -865,29 +865,58 @@ public class ExtensionHelper: ExtensionHelperType { } ) - allMessagePaths.forEach { path in - do { - let plaintext: Data = try this.read(from: path) - let message: SnodeReceivedMessage = try JSONDecoder(using: dependencies) - .decode(SnodeReceivedMessage.self, from: plaintext) - - SwarmPoller.processPollResponse( + let sortedMessages: [MessageData] = allMessagePaths + .reduce([Network.SnodeAPI.Namespace: [SnodeReceivedMessage]]()) { (result: [Network.SnodeAPI.Namespace: [SnodeReceivedMessage]], path: String) in + do { + let plaintext: Data = try this.read(from: path) + let message: SnodeReceivedMessage = try JSONDecoder(using: dependencies) + .decode(SnodeReceivedMessage.self, from: plaintext) + + return result.appending(message, toArrayOn: message.namespace) + } + catch { + failureStandardCount += 1 + Log.error(.cat, "Discarding standard message due to error: \(error)") + return result + } + } + .map { namespace, messages -> MessageData in + /// We need to sort the messages as we don't know what order they were read from disk in and some + /// messages (eg. a `VisibleMessage` and it's corresponding `UnsendRequest`) need to be + /// processed in a particular order or they won't behave correctly, luckily the `SnodeReceivedMessage.timestampMs` + /// is the "network offset" timestamp when the message was sent to the storage server (rather than the + /// "sent timestamp" on the message, which for an `UnsendRequest` will match it's associate message) + /// so we can just sort by that + ( + namespace, + messages.sorted { $0.timestampMs < $1.timestampMs }, + nil + ) + } + .sorted { lhs, rhs in lhs.namespace.processingOrder < rhs.namespace.processingOrder } + + /// Process the message (inserting into the database if needed (messages are processed per conversaiton so + /// all have the same `swarmPublicKey`) + switch sortedMessages.first?.messages.first?.swarmPublicKey { + case .none: break + case .some(let swarmPublicKey): + let (_, _, result) = SwarmPoller.processPollResponse( db, cat: .cat, source: .pushNotification, - swarmPublicKey: message.swarmPublicKey, + swarmPublicKey: swarmPublicKey, shouldStoreMessages: true, ignoreDedupeFiles: true, forceSynchronousProcessing: true, - sortedMessages: [(message.namespace, [message], nil)], + sortedMessages: sortedMessages, using: dependencies ) - successStandardCount += 1 - } - catch { - failureStandardCount += 1 - Log.error(.cat, "Discarding standard message due to error: \(error)") - } + successStandardCount += result.validMessageCount + + if result.validMessageCount != result.rawMessageCount { + failureStandardCount += (result.rawMessageCount - result.validMessageCount) + Log.error(.cat, "Discarding some standard messages due to error: \(MessageReceiverError.failedToProcess)") + } } /// Remove the standard message files now that they are processed diff --git a/SessionMessagingKit/Utilities/MessageWrapper.swift b/SessionMessagingKit/Utilities/MessageWrapper.swift index 1e7f97ba3c..3ff9796b7b 100644 --- a/SessionMessagingKit/Utilities/MessageWrapper.swift +++ b/SessionMessagingKit/Utilities/MessageWrapper.swift @@ -1,7 +1,7 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public enum MessageWrapper { diff --git a/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift b/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift index 98d331cd1d..ed46bc066c 100644 --- a/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift +++ b/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit public extension SNProtoEnvelope { diff --git a/SessionMessagingKit/Utilities/Threading+SMK.swift b/SessionMessagingKit/Utilities/Threading+SessionMessagingKit.swift similarity index 100% rename from SessionMessagingKit/Utilities/Threading+SMK.swift rename to SessionMessagingKit/Utilities/Threading+SessionMessagingKit.swift diff --git a/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift b/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift index 458c7cc357..22fb802b43 100644 --- a/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift +++ b/SessionMessagingKitTests/Database/Models/MessageDeduplicationSpec.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick @@ -18,9 +18,7 @@ class MessageDeduplicationSpec: AsyncSpec { @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies ) @TestState(singleton: .extensionHelper, in: dependencies) var mockExtensionHelper: MockExtensionHelper! = MockExtensionHelper( diff --git a/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift b/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift index 0ca3ed5345..8cdfca3459 100644 --- a/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/DisplayPictureDownloadJobSpec.swift @@ -6,7 +6,7 @@ import GRDB import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit @testable import SessionUtilitiesKit @@ -27,10 +27,7 @@ class DisplayPictureDownloadJobSpec: QuickSpec { ) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) @@ -544,7 +541,7 @@ class DisplayPictureDownloadJobSpec: QuickSpec { ) ) let expectedRequest: Network.PreparedRequest = mockStorage.read { db in - try OpenGroupAPI.preparedDownload( + try Network.SOGS.preparedDownload( fileId: "12", roomToken: "testRoom", authMethod: Authentication.community( diff --git a/SessionMessagingKitTests/Jobs/MessageSendJobSpec.swift b/SessionMessagingKitTests/Jobs/MessageSendJobSpec.swift index 15012b5318..e549e2c221 100644 --- a/SessionMessagingKitTests/Jobs/MessageSendJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/MessageSendJobSpec.swift @@ -35,10 +35,7 @@ class MessageSendJobSpec: QuickSpec { ) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try SessionThread.upsert( diff --git a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift index 39d2bff89d..42ff8e59cc 100644 --- a/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift +++ b/SessionMessagingKitTests/Jobs/RetrieveDefaultOpenGroupRoomsJobSpec.swift @@ -6,7 +6,7 @@ import GRDB import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit @testable import SessionUtilitiesKit @@ -20,10 +20,7 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) @@ -45,17 +42,22 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { MockNetwork.batchResponseData( with: [ ( - OpenGroupAPI.Endpoint.capabilities, - OpenGroupAPI.Capabilities(capabilities: [.blind, .reactions]).batchSubResponse() + Network.SOGS.Endpoint.capabilities, + Network.SOGS.CapabilitiesResponse( + capabilities: [ + Capability.Variant.blind.rawValue, + Capability.Variant.reactions.rawValue + ] + ).batchSubResponse() ), ( - OpenGroupAPI.Endpoint.rooms, + Network.SOGS.Endpoint.rooms, [ - OpenGroupAPI.Room.mock.with( + Network.SOGS.Room.mock.with( token: "testRoom", name: "TestRoomName" ), - OpenGroupAPI.Room.mock.with( + Network.SOGS.Room.mock.with( token: "testRoom2", name: "TestRoomName2", infoUpdates: 12, @@ -194,9 +196,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) } expect(openGroups?.count).to(equal(1)) - expect(openGroups?.map { $0.server }).to(equal([OpenGroupAPI.defaultServer])) + expect(openGroups?.map { $0.server }).to(equal([Network.SOGS.defaultServer])) expect(openGroups?.map { $0.roomToken }).to(equal([""])) - expect(openGroups?.map { $0.publicKey }).to(equal([OpenGroupAPI.defaultServerPublicKey])) + expect(openGroups?.map { $0.publicKey }).to(equal([Network.SOGS.defaultServerPublicKey])) expect(openGroups?.map { $0.isActive }).to(equal([false])) expect(openGroups?.map { $0.name }).to(equal([""])) } @@ -209,9 +211,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", userCount: 0, @@ -231,9 +233,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) } expect(openGroups?.count).to(equal(1)) - expect(openGroups?.map { $0.server }).to(equal([OpenGroupAPI.defaultServer])) + expect(openGroups?.map { $0.server }).to(equal([Network.SOGS.defaultServer])) expect(openGroups?.map { $0.roomToken }).to(equal([""])) - expect(openGroups?.map { $0.publicKey }).to(equal([OpenGroupAPI.defaultServerPublicKey])) + expect(openGroups?.map { $0.publicKey }).to(equal([Network.SOGS.defaultServerPublicKey])) expect(openGroups?.map { $0.isActive }).to(equal([false])) expect(openGroups?.map { $0.name }).to(equal(["TestExisting"])) } @@ -242,9 +244,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { it("sends the correct request") { mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", userCount: 0, @@ -252,17 +254,18 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { ) .insert(db) } - let expectedRequest: Network.PreparedRequest! = mockStorage.read { db in - try OpenGroupAPI.preparedCapabilitiesAndRooms( + let expectedRequest: Network.PreparedRequest! = mockStorage.read { db in + try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( info: LibSession.OpenGroupCapabilityInfo( roomToken: "", - server: OpenGroupAPI.defaultServer, - publicKey: OpenGroupAPI.defaultServerPublicKey, + server: Network.SOGS.defaultServer, + publicKey: Network.SOGS.defaultServerPublicKey, capabilities: [] ), forceBlinded: false ), + skipAuthentication: true, using: dependencies ) } @@ -284,6 +287,8 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { requestAndPathBuildTimeout: expectedRequest.requestAndPathBuildTimeout ) }) + + expect(expectedRequest?.headers).to(beEmpty()) } // MARK: -- will retry 8 times before it fails @@ -325,7 +330,7 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { let capabilities: [Capability]? = mockStorage.read { db in try Capability.fetchAll(db) } expect(capabilities?.count).to(equal(2)) expect(capabilities?.map { $0.openGroupServer }) - .to(equal([OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer])) + .to(equal([Network.SOGS.defaultServer, Network.SOGS.defaultServer])) expect(capabilities?.map { $0.variant }).to(equal([.blind, .reactions])) expect(capabilities?.map { $0.isMissing }).to(equal([false, false])) } @@ -344,13 +349,13 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) } expect(openGroups?.count).to(equal(3)) // 1 for the entry used to fetch the default rooms expect(openGroups?.map { $0.server }) - .to(equal([OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer])) + .to(equal([Network.SOGS.defaultServer, Network.SOGS.defaultServer, Network.SOGS.defaultServer])) expect(openGroups?.map { $0.roomToken }).to(equal(["", "testRoom", "testRoom2"])) expect(openGroups?.map { $0.publicKey }) .to(equal([ - OpenGroupAPI.defaultServerPublicKey, - OpenGroupAPI.defaultServerPublicKey, - OpenGroupAPI.defaultServerPublicKey + Network.SOGS.defaultServerPublicKey, + Network.SOGS.defaultServerPublicKey, + Network.SOGS.defaultServerPublicKey ])) expect(openGroups?.map { $0.isActive }).to(equal([false, false, false])) expect(openGroups?.map { $0.name }).to(equal(["", "TestRoomName", "TestRoomName2"])) @@ -360,9 +365,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { it("does not override existing rooms that were returned") { mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", userCount: 0, @@ -375,15 +380,15 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { .thenReturn( MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), ( - OpenGroupAPI.Endpoint.rooms, + Network.SOGS.Endpoint.rooms, try! JSONEncoder().with(outputFormatting: .sortedKeys).encode( Network.BatchSubResponse( code: 200, headers: [:], body: [ - OpenGroupAPI.Room.mock.with( + Network.SOGS.Room.mock.with( token: "testRoom", name: "TestReplacementName" ) @@ -408,10 +413,10 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) } expect(openGroups?.count).to(equal(2)) // 1 for the entry used to fetch the default rooms expect(openGroups?.map { $0.server }) - .to(equal([OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer])) + .to(equal([Network.SOGS.defaultServer, Network.SOGS.defaultServer])) expect(openGroups?.map { $0.roomToken }.sorted()).to(equal(["", "testRoom"])) expect(openGroups?.map { $0.publicKey }) - .to(equal([OpenGroupAPI.defaultServerPublicKey, OpenGroupAPI.defaultServerPublicKey])) + .to(equal([Network.SOGS.defaultServerPublicKey, Network.SOGS.defaultServerPublicKey])) expect(openGroups?.map { $0.isActive }).to(equal([false, false])) expect(openGroups?.map { $0.name }.sorted()).to(equal(["", "TestExisting"])) } @@ -438,7 +443,8 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { target: .community( imageId: "12", roomToken: "testRoom2", - server: OpenGroupAPI.defaultServer + server: Network.SOGS.defaultServer, + skipAuthentication: true ), timestamp: 1234567890 ) @@ -453,9 +459,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { it("schedules a display picture download if the imageId has changed") { mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom2", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", imageId: "10", @@ -485,7 +491,8 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { target: .community( imageId: "12", roomToken: "testRoom2", - server: OpenGroupAPI.defaultServer + server: Network.SOGS.defaultServer, + skipAuthentication: true ), timestamp: 1234567890 ) @@ -504,17 +511,22 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { MockNetwork.batchResponseData( with: [ ( - OpenGroupAPI.Endpoint.capabilities, - OpenGroupAPI.Capabilities(capabilities: [.blind, .reactions]).batchSubResponse() + Network.SOGS.Endpoint.capabilities, + Network.SOGS.CapabilitiesResponse( + capabilities: [ + Capability.Variant.blind.rawValue, + Capability.Variant.reactions.rawValue + ] + ).batchSubResponse() ), ( - OpenGroupAPI.Endpoint.rooms, + Network.SOGS.Endpoint.rooms, [ - OpenGroupAPI.Room.mock.with( + Network.SOGS.Room.mock.with( token: "testRoom", name: "TestRoomName" ), - OpenGroupAPI.Room.mock.with( + Network.SOGS.Room.mock.with( token: "testRoom2", name: "TestRoomName2" ) @@ -541,9 +553,9 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { it("does not schedule a display picture download if the imageId matches and the image has already been downloaded") { mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom2", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", imageId: "12", @@ -582,14 +594,14 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { .toNot(call(matchingParameters: .all) { $0.setDefaultRoomInfo([ ( - room: OpenGroupAPI.Room.mock.with( + room: Network.SOGS.Room.mock.with( token: "testRoom", name: "TestRoomName" ), openGroup: OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestRoomName", userCount: 0, @@ -597,16 +609,16 @@ class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec { ) ), ( - room: OpenGroupAPI.Room.mock.with( + room: Network.SOGS.Room.mock.with( token: "testRoom2", name: "TestRoomName2", infoUpdates: 12, imageId: "12" ), openGroup: OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom2", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestRoomName2", imageId: "12", diff --git a/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift b/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift index f9de94bac5..209274b6fd 100644 --- a/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift +++ b/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift @@ -4,12 +4,12 @@ import Foundation import GRDB import SessionUtil import SessionUtilitiesKit -import SessionSnodeKit +import SessionNetworkingKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit class LibSessionGroupInfoSpec: QuickSpec { @@ -28,11 +28,7 @@ class LibSessionGroupInfoSpec: QuickSpec { ) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self, - SNSnodeKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) @@ -886,7 +882,7 @@ class LibSessionGroupInfoSpec: QuickSpec { ) } - let expectedRequest: Network.PreparedRequest<[String: Bool]> = try SnodeAPI.preparedDeleteMessages( + let expectedRequest: Network.PreparedRequest<[String: Bool]> = try Network.SnodeAPI.preparedDeleteMessages( serverHashes: ["1234"], requireSuccessfulDeletion: false, authMethod: Authentication.groupAdmin( diff --git a/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift b/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift index 5e2884cd3e..43f6c045de 100644 --- a/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift +++ b/SessionMessagingKitTests/LibSession/LibSessionGroupMembersSpec.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit class LibSessionGroupMembersSpec: QuickSpec { @@ -27,10 +27,7 @@ class LibSessionGroupMembersSpec: QuickSpec { ) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/LibSession/LibSessionSpec.swift b/SessionMessagingKitTests/LibSession/LibSessionSpec.swift index d135064fcf..cb8ff17dd8 100644 --- a/SessionMessagingKitTests/LibSession/LibSessionSpec.swift +++ b/SessionMessagingKitTests/LibSession/LibSessionSpec.swift @@ -8,7 +8,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit class LibSessionSpec: QuickSpec { @@ -27,10 +27,7 @@ class LibSessionSpec: QuickSpec { ) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupAPISpec.swift b/SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupSpec.swift similarity index 61% rename from SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupAPISpec.swift rename to SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupSpec.swift index 35b3ba1679..7b04fa35a1 100644 --- a/SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupAPISpec.swift +++ b/SessionMessagingKitTests/Open Groups/Crypto/CryptoOpenGroupSpec.swift @@ -8,7 +8,7 @@ import Nimble @testable import SessionMessagingKit -class CryptoOpenGroupAPISpec: QuickSpec { +class CryptoOpenGroupSpec: QuickSpec { override class func spec() { // MARK: Configuration @@ -21,161 +21,8 @@ class CryptoOpenGroupAPISpec: QuickSpec { } ) - // MARK: - Crypto for OpenGroupAPI - describe("Crypto for OpenGroupAPI") { - // MARK: -- when generating a blinded15 key pair - context("when generating a blinded15 key pair") { - // MARK: ---- successfully generates - it("successfully generates") { - let result = crypto.generate( - .blinded15KeyPair( - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes - ) - ) - - // Note: The first 64 characters of the secretKey are consistent but the chars after that always differ - expect(result?.publicKey.toHexString()).to(equal(TestConstants.blind15PublicKey)) - expect(result?.secretKey.toHexString()).to(equal(TestConstants.blind15SecretKey)) - } - - // MARK: ---- fails if the edKeyPair secret key length wrong - it("fails if the ed25519SecretKey length wrong") { - let result = crypto.generate( - .blinded15KeyPair( - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Array(Data(hex: String(TestConstants.edSecretKey.prefix(4)))) - ) - ) - - expect(result).to(beNil()) - } - } - - // MARK: -- when generating a blinded25 key pair - context("when generating a blinded25 key pair") { - // MARK: ---- successfully generates - it("successfully generates") { - let result = crypto.generate( - .blinded25KeyPair( - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes - ) - ) - - // Note: The first 64 characters of the secretKey are consistent but the chars after that always differ - expect(result?.publicKey.toHexString()).to(equal(TestConstants.blind25PublicKey)) - expect(result?.secretKey.toHexString()).to(equal(TestConstants.blind25SecretKey)) - } - - // MARK: ---- fails if the edKeyPair secret key length wrong - it("fails if the ed25519SecretKey length wrong") { - let result = crypto.generate( - .blinded25KeyPair( - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Data(hex: String(TestConstants.edSecretKey.prefix(4))).bytes - ) - ) - - expect(result).to(beNil()) - } - } - - // MARK: -- when generating a signatureBlind15 - context("when generating a signatureBlind15") { - // MARK: ---- generates a correct signature - it("generates a correct signature") { - let result = crypto.generate( - .signatureBlind15( - message: "TestMessage".bytes, - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Array(Data(hex: TestConstants.edSecretKey)) - ) - ) - - expect(result?.toHexString()) - .to(equal( - "245003f1627ebdfc6099c32597d426ef84d1b301861a5ffbbac92dde6c608334" + - "ceb56a022a094a9a664fae034b50eed40bd1bfb262c7e542c979eec265ae3f07" - )) - } - } - - // MARK: -- when generating a signatureBlind25 - context("when generating a signatureBlind25") { - // MARK: ---- generates a correct signature - it("generates a correct signature") { - let result = crypto.generate( - .signatureBlind25( - message: "TestMessage".bytes, - serverPublicKey: TestConstants.serverPublicKey, - ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes - ) - ) - - expect(result?.toHexString()) - .to(equal( - "9ff9b7fb7d435c7a2c0b0b2ae64963baaf394386b9f7c7f924eeac44ec0f74c7" + - "fe6304c73a9b3a65491f81e44b545e54631e83e9a412eaed5fd4db2e05ec830c" - )) - } - } - - // MARK: -- when checking if a session id matches a blinded id - context("when checking if a session id matches a blinded id") { - // MARK: ---- returns true when a blind15 id matches - it("returns true when a blind15 id matches") { - let result = crypto.verify( - .sessionId( - "05\(TestConstants.publicKey)", - matchesBlindedId: "15\(TestConstants.blind15PublicKey)", - serverPublicKey: TestConstants.serverPublicKey - ) - ) - - expect(result).to(beTrue()) - } - - // MARK: ---- returns true when a blind25 id matches - it("returns true when a blind25 id matches") { - let result = crypto.verify( - .sessionId( - "05\(TestConstants.publicKey)", - matchesBlindedId: "25\(TestConstants.blind25PublicKey)", - serverPublicKey: TestConstants.serverPublicKey - ) - ) - - expect(result).to(beTrue()) - } - - // MARK: ---- returns false if given an invalid session id - it("returns false if given an invalid session id") { - let result = crypto.verify( - .sessionId( - "AB\(TestConstants.publicKey)", - matchesBlindedId: "15\(TestConstants.blind15PublicKey)", - serverPublicKey: TestConstants.serverPublicKey - ) - ) - - expect(result).to(beFalse()) - } - - // MARK: ---- returns false if given an invalid blinded id - it("returns false if given an invalid blinded id") { - let result = crypto.verify( - .sessionId( - "05\(TestConstants.publicKey)", - matchesBlindedId: "AB\(TestConstants.blind15PublicKey)", - serverPublicKey: TestConstants.serverPublicKey - ) - ) - - expect(result).to(beFalse()) - } - } - + // MARK: - Crypto for Open Group + describe("Crypto for Open Group") { // MARK: -- when encrypting with the session blinding protocol context("when encrypting with the session blinding protocol") { // MARK: ---- can encrypt for a blind15 recipient correctly diff --git a/SessionMessagingKitTests/Open Groups/Models/CapabilitiesSpec.swift b/SessionMessagingKitTests/Open Groups/Models/CapabilitySpec.swift similarity index 73% rename from SessionMessagingKitTests/Open Groups/Models/CapabilitiesSpec.swift rename to SessionMessagingKitTests/Open Groups/Models/CapabilitySpec.swift index 0fc98cf1a8..a3a918bcff 100644 --- a/SessionMessagingKitTests/Open Groups/Models/CapabilitiesSpec.swift +++ b/SessionMessagingKitTests/Open Groups/Models/CapabilitySpec.swift @@ -7,34 +7,8 @@ import Nimble @testable import SessionMessagingKit -class CapabilitiesSpec: QuickSpec { +class CapabilitySpec: QuickSpec { override class func spec() { - // MARK: - Capabilities - describe("Capabilities") { - // MARK: -- when initializing - context("when initializing") { - // MARK: ---- assigns values correctly - it("assigns values correctly") { - let capabilities: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities( - capabilities: [.sogs], - missing: [.sogs] - ) - - expect(capabilities.capabilities).to(equal([.sogs])) - expect(capabilities.missing).to(equal([.sogs])) - } - - it("defaults missing to nil") { - let capabilities: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities( - capabilities: [.sogs] - ) - - expect(capabilities.capabilities).to(equal([.sogs])) - expect(capabilities.missing).to(beNil()) - } - } - } - // MARK: - a Capability describe("a Capability") { // MARK: -- when initializing diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift index de38e4e215..f0933e8d92 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift @@ -4,13 +4,13 @@ import UIKit import Combine import GRDB import SessionUtil -import SessionSnodeKit import SessionUtilitiesKit import Quick import Nimble @testable import SessionMessagingKit +@testable import SessionNetworkingKit class OpenGroupManagerSpec: QuickSpec { override class func spec() { @@ -61,12 +61,12 @@ class OpenGroupManagerSpec: QuickSpec { infoUpdates: 10, sequenceNumber: 5 ) - @TestState var testPollInfo: OpenGroupAPI.RoomPollInfo! = OpenGroupAPI.RoomPollInfo.mock.with( + @TestState var testPollInfo: Network.SOGS.RoomPollInfo! = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, details: .mock ) - @TestState var testMessage: OpenGroupAPI.Message! = OpenGroupAPI.Message( + @TestState var testMessage: Network.SOGS.Message! = Network.SOGS.Message( id: 127, sender: "05\(TestConstants.publicKey)", posted: 123, @@ -92,14 +92,14 @@ class OpenGroupManagerSpec: QuickSpec { base64EncodedSignature: nil, reactions: nil ) - @TestState var testDirectMessage: OpenGroupAPI.DirectMessage! = { + @TestState var testDirectMessage: Network.SOGS.DirectMessage! = { let proto = SNProtoContent.builder() let protoDataBuilder = SNProtoDataMessage.builder() proto.setSigTimestamp(1234567890000) protoDataBuilder.setBody("TestMessage") proto.setDataMessage(try! protoDataBuilder.build()) - return OpenGroupAPI.DirectMessage( + return Network.SOGS.DirectMessage( id: 128, sender: "15\(TestConstants.blind15PublicKey)", recipient: "15\(TestConstants.blind15PublicKey)", @@ -110,10 +110,7 @@ class OpenGroupManagerSpec: QuickSpec { }() @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) @@ -972,7 +969,7 @@ class OpenGroupManagerSpec: QuickSpec { mockStorage.write { db in try OpenGroup.deleteAll(db) try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom", publicKey: TestConstants.publicKey, isActive: true, @@ -986,7 +983,7 @@ class OpenGroupManagerSpec: QuickSpec { outboxLatestMessageId: 0 ).insert(db) try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "testRoom1", publicKey: TestConstants.publicKey, isActive: true, @@ -1007,7 +1004,7 @@ class OpenGroupManagerSpec: QuickSpec { mockStorage.write { db in try openGroupManager.delete( db, - openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer), + openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: Network.SOGS.defaultServer), skipLibSessionUpdate: true ) } @@ -1021,7 +1018,7 @@ class OpenGroupManagerSpec: QuickSpec { mockStorage.write { db in try openGroupManager.delete( db, - openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer), + openGroupId: OpenGroup.idFor(roomToken: "testRoom", server: Network.SOGS.defaultServer), skipLibSessionUpdate: true ) } @@ -1030,7 +1027,7 @@ class OpenGroupManagerSpec: QuickSpec { mockStorage.read { db in try OpenGroup .select(.isActive) - .filter(id: OpenGroup.idFor(roomToken: "testRoom", server: OpenGroupAPI.defaultServer)) + .filter(id: OpenGroup.idFor(roomToken: "testRoom", server: Network.SOGS.defaultServer)) .asRequest(of: Bool.self) .fetchOne(db) } @@ -1046,7 +1043,10 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager .handleCapabilities( db, - capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: []), + capabilities: Network.SOGS.CapabilitiesResponse( + capabilities: ["sogs"], + missing: [] + ), on: "http://127.0.0.1" ) } @@ -1183,10 +1183,10 @@ class OpenGroupManagerSpec: QuickSpec { context("and updating the moderator list") { // MARK: ------ successfully updates it("successfully updates") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( moderators: ["TestMod"], hiddenModerators: [], admins: [], @@ -1230,10 +1230,10 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ------ updates for hidden moderators it("updates for hidden moderators") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( moderators: [], hiddenModerators: ["TestMod2"], admins: [], @@ -1277,7 +1277,7 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ------ does not insert mods if no moderators are provided it("does not insert mods if no moderators are provided") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10 ) @@ -1302,10 +1302,10 @@ class OpenGroupManagerSpec: QuickSpec { context("and updating the admin list") { // MARK: ------ successfully updates it("successfully updates") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( moderators: [], hiddenModerators: [], admins: ["TestAdmin"], @@ -1349,10 +1349,10 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ------ updates for hidden admins it("updates for hidden admins") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( moderators: [], hiddenModerators: [], admins: [], @@ -1396,7 +1396,7 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ------ does not insert an admin if no admins are provided it("does not insert an admin if no admins are provided") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, details: nil @@ -1478,10 +1478,10 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ------ schedules a download for the room image it("schedules a download for the room image") { - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( token: "test", name: "test", imageId: "10" @@ -1546,7 +1546,7 @@ class OpenGroupManagerSpec: QuickSpec { ).insert(db) } - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, details: nil @@ -1599,10 +1599,10 @@ class OpenGroupManagerSpec: QuickSpec { ).insert(db) } - testPollInfo = OpenGroupAPI.RoomPollInfo.mock.with( + testPollInfo = Network.SOGS.RoomPollInfo.mock.with( token: "testRoom", activeUsers: 10, - details: OpenGroupAPI.Room.mock.with( + details: Network.SOGS.Room.mock.with( token: "test", name: "test", infoUpdates: 10, @@ -1703,7 +1703,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 1, sender: nil, posted: 123, @@ -1766,7 +1766,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 1, sender: nil, posted: 123, @@ -1800,7 +1800,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 1, sender: "05\(TestConstants.publicKey)", posted: 123, @@ -1845,7 +1845,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 2, sender: "05\(TestConstants.publicKey)", posted: 122, @@ -1886,7 +1886,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 127, sender: "05\(TestConstants.publicKey)", posted: 123, @@ -1916,7 +1916,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleMessages( db, messages: [ - OpenGroupAPI.Message( + Network.SOGS.Message( id: 127, sender: "05\(TestConstants.publicKey)", posted: 123, @@ -2033,7 +2033,7 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ---- ignores messages with non base64 encoded data it("ignores messages with non base64 encoded data") { - testDirectMessage = OpenGroupAPI.DirectMessage( + testDirectMessage = Network.SOGS.DirectMessage( id: testDirectMessage.id, sender: testDirectMessage.sender.replacingOccurrences(of: "8", with: "9"), recipient: testDirectMessage.recipient, @@ -2137,7 +2137,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleDirectMessages( db, messages: [ - OpenGroupAPI.DirectMessage( + Network.SOGS.DirectMessage( id: testDirectMessage.id, sender: testDirectMessage.sender.replacingOccurrences(of: "8", with: "9"), recipient: testDirectMessage.recipient, @@ -2293,7 +2293,7 @@ class OpenGroupManagerSpec: QuickSpec { OpenGroupManager.handleDirectMessages( db, messages: [ - OpenGroupAPI.DirectMessage( + Network.SOGS.DirectMessage( id: testDirectMessage.id, sender: testDirectMessage.sender.replacingOccurrences(of: "8", with: "9"), recipient: testDirectMessage.recipient, @@ -2529,9 +2529,9 @@ class OpenGroupManagerSpec: QuickSpec { .thenReturn(true) mockStorage.write { db in try OpenGroup( - server: OpenGroupAPI.defaultServer, + server: Network.SOGS.defaultServer, roomToken: "", - publicKey: OpenGroupAPI.defaultServerPublicKey, + publicKey: Network.SOGS.defaultServerPublicKey, isActive: false, name: "TestExisting", userCount: 0, @@ -2539,13 +2539,13 @@ class OpenGroupManagerSpec: QuickSpec { ) .insert(db) } - let expectedRequest: Network.PreparedRequest! = mockStorage.read { db in - try OpenGroupAPI.preparedCapabilitiesAndRooms( + let expectedRequest: Network.PreparedRequest! = mockStorage.read { db in + try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( info: LibSession.OpenGroupCapabilityInfo( roomToken: "", - server: OpenGroupAPI.defaultServer, - publicKey: OpenGroupAPI.defaultServerPublicKey, + server: Network.SOGS.defaultServer, + publicKey: Network.SOGS.defaultServerPublicKey, capabilities: [] ), forceBlinded: false @@ -2569,7 +2569,7 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: ---- does not start a job to retrieve the default rooms if we already have rooms it("does not start a job to retrieve the default rooms if we already have rooms") { mockAppGroupDefaults.when { $0.bool(forKey: UserDefaults.BoolKey.isMainAppActive.rawValue) }.thenReturn(true) - cache.setDefaultRoomInfo([(room: OpenGroupAPI.Room.mock, openGroup: OpenGroup.mock)]) + cache.setDefaultRoomInfo([(room: Network.SOGS.Room.mock, openGroup: OpenGroup.mock)]) cache.defaultRoomsPublisher.sinkUntilComplete() expect(mockNetwork) @@ -2582,7 +2582,7 @@ class OpenGroupManagerSpec: QuickSpec { // MARK: - Convenience Extensions -extension OpenGroupAPI.Room { +extension Network.SOGS.Room { func with( token: String? = nil, name: String? = nil, @@ -2592,8 +2592,8 @@ extension OpenGroupAPI.Room { hiddenModerators: [String]? = nil, admins: [String]? = nil, hiddenAdmins: [String]? = nil - ) -> OpenGroupAPI.Room { - return OpenGroupAPI.Room( + ) -> Network.SOGS.Room { + return Network.SOGS.Room( token: (token ?? self.token), name: (name ?? self.name), roomDescription: self.roomDescription, @@ -2623,13 +2623,13 @@ extension OpenGroupAPI.Room { } } -extension OpenGroupAPI.RoomPollInfo { +extension Network.SOGS.RoomPollInfo { func with( token: String? = nil, activeUsers: Int64? = nil, - details: OpenGroupAPI.Room? = .mock - ) -> OpenGroupAPI.RoomPollInfo { - return OpenGroupAPI.RoomPollInfo( + details: Network.SOGS.Room? = .mock + ) -> Network.SOGS.RoomPollInfo { + return Network.SOGS.RoomPollInfo( token: (token ?? self.token), activeUsers: (activeUsers ?? self.activeUsers), admin: self.admin, @@ -2661,133 +2661,49 @@ extension OpenGroup: Mocked { infoUpdates: 0 ) } - -extension OpenGroupAPI.Capabilities: Mocked { - static var mock: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities(capabilities: [], missing: nil) -} - -extension OpenGroupAPI.Room: Mocked { - static var mock: OpenGroupAPI.Room = OpenGroupAPI.Room( - token: "test", - name: "testRoom", - roomDescription: nil, - infoUpdates: 1, - messageSequence: 1, - created: 1, - activeUsers: 1, - activeUsersCutoff: 1, - imageId: nil, - pinnedMessages: nil, - admin: false, - globalAdmin: false, - admins: [], - hiddenAdmins: nil, - moderator: false, - globalModerator: false, - moderators: [], - hiddenModerators: nil, - read: true, - defaultRead: nil, - defaultAccessible: nil, - write: true, - defaultWrite: nil, - upload: true, - defaultUpload: nil - ) -} - -extension OpenGroupAPI.RoomPollInfo: Mocked { - static var mock: OpenGroupAPI.RoomPollInfo = OpenGroupAPI.RoomPollInfo( - token: "test", - activeUsers: 1, - admin: false, - globalAdmin: false, - moderator: false, - globalModerator: false, - read: true, - defaultRead: nil, - defaultAccessible: nil, - write: true, - defaultWrite: nil, - upload: true, - defaultUpload: false, - details: .mock - ) -} - -extension OpenGroupAPI.Message: Mocked { - static var mock: OpenGroupAPI.Message = OpenGroupAPI.Message( - id: 100, - sender: TestConstants.blind15PublicKey, - posted: 1, - edited: nil, - deleted: nil, - seqNo: 1, - whisper: false, - whisperMods: false, - whisperTo: nil, - base64EncodedData: nil, - base64EncodedSignature: nil, - reactions: nil - ) -} - -extension OpenGroupAPI.SendDirectMessageResponse: Mocked { - static var mock: OpenGroupAPI.SendDirectMessageResponse = OpenGroupAPI.SendDirectMessageResponse( - id: 1, - sender: TestConstants.blind15PublicKey, - recipient: "testRecipient", - posted: 1122, - expires: 2233 - ) -} - -extension OpenGroupAPI.DirectMessage: Mocked { - static var mock: OpenGroupAPI.DirectMessage = OpenGroupAPI.DirectMessage( - id: 101, - sender: TestConstants.blind15PublicKey, - recipient: "testRecipient", - posted: 1212, - expires: 2323, - base64EncodedMessage: "TestMessage".data(using: .utf8)!.base64EncodedString() - ) -} extension Network.BatchResponse { static let mockUnblindedPollResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.roomPollInfo("testRoom", 0), OpenGroupAPI.RoomPollInfo.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.roomMessagesRecent("testRoom"), [OpenGroupAPI.Message].mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.roomPollInfo("testRoom", 0), Network.SOGS.RoomPollInfo.mockBatchSubResponse()), + (Network.SOGS.Endpoint.roomMessagesRecent("testRoom"), [Network.SOGS.Message].mockBatchSubResponse()) ] ) static let mockBlindedPollResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.roomPollInfo("testRoom", 0), OpenGroupAPI.RoomPollInfo.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.roomMessagesRecent("testRoom"), OpenGroupAPI.Message.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.inboxSince(id: 0), OpenGroupAPI.DirectMessage.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.outboxSince(id: 0), OpenGroupAPI.DirectMessage.self.mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.roomPollInfo("testRoom", 0), Network.SOGS.RoomPollInfo.mockBatchSubResponse()), + (Network.SOGS.Endpoint.roomMessagesRecent("testRoom"), Network.SOGS.Message.mockBatchSubResponse()), + (Network.SOGS.Endpoint.inboxSince(id: 0), Network.SOGS.DirectMessage.mockBatchSubResponse()), + (Network.SOGS.Endpoint.outboxSince(id: 0), Network.SOGS.DirectMessage.self.mockBatchSubResponse()) ] ) static let mockCapabilitiesResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()) ] ) static let mockRoomResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Room.mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.Room.mockBatchSubResponse()) ] ) static let mockBanAndDeleteAllResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.roomDeleteMessages("testRoon", sessionId: ""), NoResponse.mockBatchSubResponse()) + (Network.SOGS.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.roomDeleteMessages("testRoon", sessionId: ""), NoResponse.mockBatchSubResponse()) + ] + ) + + static let mockCapabilitiesAndRoomResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( + with: [ + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.room("testRoom"), Network.SOGS.Room.mockBatchSubResponse()) ] ) } diff --git a/SessionMessagingKitTests/Open Groups/Types/SOGSErrorSpec.swift b/SessionMessagingKitTests/Open Groups/Types/SOGSErrorSpec.swift deleted file mode 100644 index 7dba9ab779..0000000000 --- a/SessionMessagingKitTests/Open Groups/Types/SOGSErrorSpec.swift +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -import Quick -import Nimble - -@testable import SessionMessagingKit - -class SOGSErrorSpec: QuickSpec { - override class func spec() { - // MARK: - a SOGSError - describe("a SOGSError") { - // MARK: -- generates the error description correctly - it("generates the error description correctly") { - expect(OpenGroupAPIError.decryptionFailed.description).to(equal("Couldn't decrypt response.")) - expect(OpenGroupAPIError.signingFailed.description).to(equal("Couldn't sign message.")) - expect(OpenGroupAPIError.noPublicKey.description).to(equal("Couldn't find server public key.")) - expect(OpenGroupAPIError.invalidEmoji.description).to(equal("The emoji is invalid.")) - expect(OpenGroupAPIError.invalidPoll.description).to(equal("Poller in invalid state.")) - } - } - } -} diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift index debc33045b..e42508adee 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift @@ -9,7 +9,7 @@ import SessionUtil import SessionUtilitiesKit import SessionUIKit -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit class MessageReceiverGroupsSpec: QuickSpec { @@ -29,11 +29,7 @@ class MessageReceiverGroupsSpec: QuickSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) @@ -142,7 +138,7 @@ class MessageReceiverGroupsSpec: QuickSpec { .thenReturn(Data([1, 2, 3])) keychain .when { try $0.data(forKey: .pushNotificationEncryptionKey) } - .thenReturn(Data((0.. = mockStorage.write { db in + let expectedRequest: Network.PreparedRequest = mockStorage.write { db in _ = try SessionThread.upsert( db, id: groupId.hexString, @@ -860,10 +856,17 @@ class MessageReceiverGroupsSpec: QuickSpec { groupIdentityPrivateKey: groupSecretKey, invited: nil ).upsert(db) - let result = try PushNotificationAPI.preparedSubscribe( - db, + let result = try Network.PushNotification.preparedSubscribe( token: Data([5, 4, 3, 2, 1]), - sessionIds: [groupId], + swarms: [ + ( + groupId, + Authentication.groupAdmin( + groupSessionId: groupId, + ed25519SecretKey: Array(groupSecretKey) + ) + ) + ], using: dependencies ) @@ -914,7 +917,7 @@ class MessageReceiverGroupsSpec: QuickSpec { // MARK: -------- subscribes for push notifications it("subscribes for push notifications") { - let expectedRequest: Network.PreparedRequest = mockStorage.write { db in + let expectedRequest: Network.PreparedRequest = mockStorage.write { db in _ = try SessionThread.upsert( db, id: groupId.hexString, @@ -933,10 +936,17 @@ class MessageReceiverGroupsSpec: QuickSpec { authData: inviteMessage.memberAuthData, invited: nil ).upsert(db) - let result = try PushNotificationAPI.preparedSubscribe( - db, + let result = try Network.PushNotification.preparedSubscribe( token: Data(hex: Data([5, 4, 3, 2, 1]).toHexString()), - sessionIds: [groupId], + swarms: [ + ( + groupId, + Authentication.groupMember( + groupSessionId: groupId, + authData: inviteMessage.memberAuthData + ) + ) + ], using: dependencies ) @@ -2825,7 +2835,7 @@ class MessageReceiverGroupsSpec: QuickSpec { deleteContentMessage.sender = "051111111111111111111111111111111111111111111111111111111111111112" deleteContentMessage.sentTimestampMs = 1234567800000 - let preparedRequest: Network.PreparedRequest<[String: Bool]> = try! SnodeAPI + let preparedRequest: Network.PreparedRequest<[String: Bool]> = try! Network.SnodeAPI .preparedDeleteMessages( serverHashes: ["TestMessageHash3"], requireSuccessfulDeletion: false, @@ -3096,11 +3106,18 @@ class MessageReceiverGroupsSpec: QuickSpec { .when { $0.bool(forKey: UserDefaults.BoolKey.isUsingFullAPNs.rawValue) } .thenReturn(true) - let expectedRequest: Network.PreparedRequest = mockStorage.read { db in - try PushNotificationAPI.preparedUnsubscribe( - db, + let expectedRequest: Network.PreparedRequest = mockStorage.read { db in + try Network.PushNotification.preparedUnsubscribe( token: Data([5, 4, 3, 2, 1]), - sessionIds: [groupId], + swarms: [ + ( + groupId, + Authentication.groupMember( + groupSessionId: groupId, + authData: Data([1, 2, 3]) + ) + ) + ], using: dependencies ) }! diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift index 1bd4a1b614..ddebdba938 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift @@ -10,7 +10,7 @@ import Quick import Nimble @testable import SessionMessagingKit -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class MessageSenderGroupsSpec: QuickSpec { override class func spec() { @@ -29,10 +29,7 @@ class MessageSenderGroupsSpec: QuickSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) @@ -162,7 +159,7 @@ class MessageSenderGroupsSpec: QuickSpec { .thenReturn(Data([1, 2, 3])) keychain .when { try $0.data(forKey: .pushNotificationEncryptionKey) } - .thenReturn(Data((0.. = try SnodeAPI.preparedSequence( + let preparedRequest: Network.PreparedRequest = try Network.SnodeAPI.preparedSequence( requests: [ - try SnodeAPI + try Network.SnodeAPI .preparedSendMessage( message: SnodeMessage( recipient: groupId.hexString, @@ -734,7 +731,7 @@ class MessageSenderGroupsSpec: QuickSpec { // MARK: ------ and trying to subscribe for push notifications context("and trying to subscribe for push notifications") { - @TestState var expectedRequest: Network.PreparedRequest! + @TestState var expectedRequest: Network.PreparedRequest! beforeEach { // Need to set `isUsingFullAPNs` to true to generate the `expectedRequest` @@ -763,10 +760,17 @@ class MessageSenderGroupsSpec: QuickSpec { groupIdentityPrivateKey: groupSecretKey, invited: nil ).upsert(db) - let result = try PushNotificationAPI.preparedSubscribe( - db, + let result = try Network.PushNotification.preparedSubscribe( token: Data([5, 4, 3, 2, 1]), - sessionIds: [groupId], + swarms: [ + ( + groupId, + Authentication.groupAdmin( + groupSessionId: groupId, + ed25519SecretKey: Array(groupSecretKey) + ) + ) + ], using: dependencies ) @@ -1027,9 +1031,9 @@ class MessageSenderGroupsSpec: QuickSpec { "LPczVOFKOPs+rrB3aUpMsNUnJHOEhW9g6zi/UPjuCWTnnvpxlMTpHaTFlMTp+NjQ6dKi86jZJ" + "l3oiJEA5h5pBE5oOJHQNvtF8GOcsYwrIFTZKnI7AGkBSu1TxP0xLWwTUzjOGMgmKvlIgkQ6e9" + "r3JBmU=" - let expectedRequest: Network.PreparedRequest = try SnodeAPI.preparedSequence( + let expectedRequest: Network.PreparedRequest = try Network.SnodeAPI.preparedSequence( requests: [] - .appending(try SnodeAPI.preparedUnrevokeSubaccounts( + .appending(try Network.SnodeAPI.preparedUnrevokeSubaccounts( subaccountsToUnrevoke: [Array("TestSubAccountToken".data(using: .utf8)!)], authMethod: Authentication.groupAdmin( groupSessionId: groupId, @@ -1037,7 +1041,7 @@ class MessageSenderGroupsSpec: QuickSpec { ), using: dependencies )) - .appending(try SnodeAPI.preparedSendMessage( + .appending(try Network.SnodeAPI.preparedSendMessage( message: SnodeMessage( recipient: groupId.hexString, data: Data(base64Encoded: requestDataString)!, @@ -1051,7 +1055,7 @@ class MessageSenderGroupsSpec: QuickSpec { ), using: dependencies )) - .appending(try SnodeAPI.preparedDeleteMessages( + .appending(try Network.SnodeAPI.preparedDeleteMessages( serverHashes: ["testHash"], requireSuccessfulDeletion: false, authMethod: Authentication.groupAdmin( @@ -1242,9 +1246,9 @@ class MessageSenderGroupsSpec: QuickSpec { // MARK: ---- includes the unrevoke subaccounts as part of the config sync sequence it("includes the unrevoke subaccounts as part of the config sync sequence") { - let expectedRequest: Network.PreparedRequest = try SnodeAPI.preparedSequence( + let expectedRequest: Network.PreparedRequest = try Network.SnodeAPI.preparedSequence( requests: [] - .appending(try SnodeAPI + .appending(try Network.SnodeAPI .preparedUnrevokeSubaccounts( subaccountsToUnrevoke: [Array("TestSubAccountToken".data(using: .utf8)!)], authMethod: Authentication.groupAdmin( @@ -1254,7 +1258,7 @@ class MessageSenderGroupsSpec: QuickSpec { using: dependencies ) ) - .appending(try SnodeAPI.preparedDeleteMessages( + .appending(try Network.SnodeAPI.preparedDeleteMessages( serverHashes: ["testHash"], requireSuccessfulDeletion: false, authMethod: Authentication.groupAdmin( @@ -1475,25 +1479,25 @@ extension Network.BatchResponse { fileprivate static let mockConfigSyncResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) + (Network.SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) ] ) fileprivate static let mockAddMemberConfigSyncResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (SnodeAPI.Endpoint.unrevokeSubaccount, UnrevokeSubaccountResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) + (Network.SnodeAPI.Endpoint.unrevokeSubaccount, UnrevokeSubaccountResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) ] ) fileprivate static let mockAddMemberHistoricConfigSyncResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (SnodeAPI.Endpoint.unrevokeSubaccount, UnrevokeSubaccountResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), - (SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) + (Network.SnodeAPI.Endpoint.unrevokeSubaccount, UnrevokeSubaccountResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.sendMessage, SendMessagesResponse.mockBatchSubResponse()), + (Network.SnodeAPI.Endpoint.deleteMessages, DeleteMessagesResponse.mockBatchSubResponse()) ] ) } diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift index 5e67562442..ee73e9fbc0 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderSpec.swift @@ -2,7 +2,7 @@ import Foundation import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick @@ -17,10 +17,7 @@ class MessageSenderSpec: QuickSpec { @TestState var dependencies: TestDependencies! = TestDependencies() @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db) diff --git a/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift b/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift index 742f51d783..79f98b853e 100644 --- a/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/Pollers/CommunityPollerSpec.swift @@ -3,7 +3,7 @@ import UIKit import Combine import GRDB -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick @@ -21,10 +21,7 @@ class CommunityPollerSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) diff --git a/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift b/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift index 16d88729ea..13dd801e31 100644 --- a/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift +++ b/SessionMessagingKitTests/Utilities/ExtensionHelperSpec.swift @@ -3,7 +3,7 @@ import Foundation import GRDB import SessionUtil -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import Quick @@ -24,10 +24,7 @@ class ExtensionHelperSpec: AsyncSpec { @TestState(singleton: .extensionHelper, in: dependencies) var extensionHelper: ExtensionHelper! = ExtensionHelper(using: dependencies) @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNMessagingKit.self - ], + migrations: SNMessagingKit.migrations, using: dependencies ) @TestState(singleton: .crypto, in: dependencies) var mockCrypto: MockCrypto! = MockCrypto( @@ -738,7 +735,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2332,7 +2329,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2369,7 +2366,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2385,7 +2382,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2422,7 +2419,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2438,7 +2435,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) @@ -2480,7 +2477,7 @@ class ExtensionHelperSpec: AsyncSpec { categories: [ Log.Category.create( "ExtensionHelper", - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .info ) diff --git a/SessionMessagingKitTests/_TestUtilities/CustomArgSummaryDescribable+SMK.swift b/SessionMessagingKitTests/_TestUtilities/CustomArgSummaryDescribable+SessionMessagingKit.swift similarity index 100% rename from SessionMessagingKitTests/_TestUtilities/CustomArgSummaryDescribable+SMK.swift rename to SessionMessagingKitTests/_TestUtilities/CustomArgSummaryDescribable+SessionMessagingKit.swift diff --git a/SessionMessagingKitTests/_TestUtilities/MockExtensionHelper.swift b/SessionMessagingKitTests/_TestUtilities/MockExtensionHelper.swift index e68a787acd..06a4562e50 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockExtensionHelper.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockExtensionHelper.swift @@ -1,7 +1,7 @@ // Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit @testable import SessionMessagingKit diff --git a/SessionMessagingKitTests/_TestUtilities/MockImageDataManager.swift b/SessionMessagingKitTests/_TestUtilities/MockImageDataManager.swift index d409bede10..78693da700 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockImageDataManager.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockImageDataManager.swift @@ -12,9 +12,10 @@ class MockImageDataManager: Mock, ImageDataManagerType { return mock(args: [source]) } + @MainActor func load( _ source: ImageDataManager.DataSource, - onComplete: @escaping (ImageDataManager.ProcessedImageData?) -> Void + onComplete: @MainActor @escaping (ImageDataManager.ProcessedImageData?) -> Void ) { mockNoReturn(args: [source], untrackedArgs: [onComplete]) } diff --git a/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift b/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift index a7db4da5de..191d60c4d0 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift @@ -11,7 +11,7 @@ class MockOGMCache: Mock, OGMCacheType { mock() } - var pendingChanges: [OpenGroupAPI.PendingChange] { + var pendingChanges: [OpenGroupManager.PendingChange] { get { return mock() } set { mockNoReturn(args: [newValue]) } } diff --git a/SessionMessagingKitTests/_TestUtilities/MockPoller.swift b/SessionMessagingKitTests/_TestUtilities/MockPoller.swift index b3f044e3bd..cfc5968bd2 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockPoller.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockPoller.swift @@ -2,7 +2,7 @@ import Foundation import Combine -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit @testable import SessionMessagingKit @@ -43,7 +43,7 @@ class MockPoller: Mock, PollerType { pollerQueue: DispatchQueue, pollerDestination: PollerDestination, pollerDrainBehaviour: ThreadSafeObject, - namespaces: [SnodeAPI.Namespace], + namespaces: [Network.SnodeAPI.Namespace], failureCount: Int, shouldStoreMessages: Bool, logStartAndStopCalls: Bool, diff --git a/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift b/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift index 43ab428f96..05af305032 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockSwarmPoller.swift @@ -2,7 +2,7 @@ import Foundation import Combine -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit @testable import SessionMessagingKit @@ -39,7 +39,7 @@ class MockSwarmPoller: Mock, SwarmPollerType & Pol pollerQueue: DispatchQueue, pollerDestination: PollerDestination, pollerDrainBehaviour: ThreadSafeObject, - namespaces: [SnodeAPI.Namespace], + namespaces: [Network.SnodeAPI.Namespace], failureCount: Int, shouldStoreMessages: Bool, logStartAndStopCalls: Bool, diff --git a/SessionNetworkingKit/Crypto/Crypto+SessionNetworkingKit.swift b/SessionNetworkingKit/Crypto/Crypto+SessionNetworkingKit.swift new file mode 100644 index 0000000000..dad09fce75 --- /dev/null +++ b/SessionNetworkingKit/Crypto/Crypto+SessionNetworkingKit.swift @@ -0,0 +1,61 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtil +import SessionUtilitiesKit + +// MARK: - ONS Response + +internal extension Crypto.Generator { + static func sessionId( + name: String, + response: Network.SnodeAPI.ONSResolveResponse + ) -> Crypto.Generator { + return Crypto.Generator( + id: "sessionId_for_ONS_response", + args: [name, response] + ) { + guard var cName: [CChar] = name.lowercased().cString(using: .utf8) else { + throw SnodeAPIError.onsDecryptionFailed + } + + // Name must be in lowercase + var cCiphertext: [UInt8] = Array(Data(hex: response.result.encryptedValue)) + var cSessionId: [CChar] = [CChar](repeating: 0, count: 67) + + // Need to switch on `result.nonce` and explciitly pass `nil` because passing an optional + // to a C function doesn't seem to work correctly + switch response.result.nonce { + case .none: + guard + session_decrypt_ons_response( + &cName, + &cCiphertext, + cCiphertext.count, + nil, + &cSessionId + ) + else { throw SnodeAPIError.onsDecryptionFailed } + + case .some(let nonce): + var cNonce: [UInt8] = Array(Data(hex: nonce)) + + guard + cNonce.count == 24, + session_decrypt_ons_response( + &cName, + &cCiphertext, + cCiphertext.count, + &cNonce, + &cSessionId + ) + else { throw SnodeAPIError.onsDecryptionFailed } + } + + return String(cString: cSessionId) + } + } +} + diff --git a/SessionSnodeKit/Crypto/Crypto+SessionSnodeKit.swift b/SessionNetworkingKit/FileServer/Crypto/Crypto+FileServer.swift similarity index 58% rename from SessionSnodeKit/Crypto/Crypto+SessionSnodeKit.swift rename to SessionNetworkingKit/FileServer/Crypto/Crypto+FileServer.swift index 94fed80795..2dced50d25 100644 --- a/SessionSnodeKit/Crypto/Crypto+SessionSnodeKit.swift +++ b/SessionNetworkingKit/FileServer/Crypto/Crypto+FileServer.swift @@ -1,4 +1,4 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. // // stringlint:disable @@ -6,59 +6,6 @@ import Foundation import SessionUtil import SessionUtilitiesKit -// MARK: - ONS Response - -internal extension Crypto.Generator { - static func sessionId( - name: String, - response: SnodeAPI.ONSResolveResponse - ) -> Crypto.Generator { - return Crypto.Generator( - id: "sessionId_for_ONS_response", - args: [name, response] - ) { - guard var cName: [CChar] = name.lowercased().cString(using: .utf8) else { - throw SnodeAPIError.onsDecryptionFailed - } - - // Name must be in lowercase - var cCiphertext: [UInt8] = Array(Data(hex: response.result.encryptedValue)) - var cSessionId: [CChar] = [CChar](repeating: 0, count: 67) - - // Need to switch on `result.nonce` and explciitly pass `nil` because passing an optional - // to a C function doesn't seem to work correctly - switch response.result.nonce { - case .none: - guard - session_decrypt_ons_response( - &cName, - &cCiphertext, - cCiphertext.count, - nil, - &cSessionId - ) - else { throw SnodeAPIError.onsDecryptionFailed } - - case .some(let nonce): - var cNonce: [UInt8] = Array(Data(hex: nonce)) - - guard - cNonce.count == 24, - session_decrypt_ons_response( - &cName, - &cCiphertext, - cCiphertext.count, - &cNonce, - &cSessionId - ) - else { throw SnodeAPIError.onsDecryptionFailed } - } - - return String(cString: cSessionId) - } - } -} - // MARK: - Version Blinded ID public extension Crypto.Generator { diff --git a/SessionNetworkingKit/FileServer/FileServer.swift b/SessionNetworkingKit/FileServer/FileServer.swift new file mode 100644 index 0000000000..90fdec9e08 --- /dev/null +++ b/SessionNetworkingKit/FileServer/FileServer.swift @@ -0,0 +1,91 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtilitiesKit + +public extension Network { + enum FileServer { + internal static let fileServer = "http://filev2.getsession.org" + internal static let fileServerPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" + internal static let legacyFileServer = "http://88.99.175.227" + internal static let legacyFileServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" + + static func fileServerPubkey(url: String? = nil) -> String { + switch url?.contains(legacyFileServer) { + case true: return legacyFileServerPublicKey + default: return fileServerPublicKey + } + } + + static func isFileServerUrl(url: URL) -> Bool { + return ( + url.absoluteString.starts(with: fileServer) || + url.absoluteString.starts(with: legacyFileServer) + ) + } + + public static func downloadUrlString(for url: String, fileId: String) -> String { + switch url.contains(legacyFileServer) { + case true: return "\(fileServer)/\(Endpoint.fileIndividual(fileId).path)" + default: return downloadUrlString(for: fileId) + } + } + + public static func downloadUrlString(for fileId: String) -> String { + return "\(fileServer)/\(Endpoint.fileIndividual(fileId).path)" + } + + public static func fileId(for downloadUrl: String?) -> String? { + return downloadUrl + .map { urlString -> String? in + urlString + .split(separator: "/") // stringlint:ignore + .last + .map { String($0) } + } + } + } + + static func preparedUpload( + data: Data, + requestAndPathBuildTimeout: TimeInterval? = nil, + using dependencies: Dependencies + ) throws -> PreparedRequest { + return try PreparedRequest( + request: Request( + endpoint: FileServer.Endpoint.file, + destination: .serverUpload( + server: FileServer.fileServer, + x25519PublicKey: FileServer.fileServerPublicKey, + fileName: nil + ), + body: data + ), + responseType: FileUploadResponse.self, + requestTimeout: Network.fileUploadTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) + } + + static func preparedDownload( + url: URL, + using dependencies: Dependencies + ) throws -> PreparedRequest { + return try PreparedRequest( + request: Request( + endpoint: FileServer.Endpoint.directUrl(url), + destination: .serverDownload( + url: url, + x25519PublicKey: FileServer.fileServerPublicKey, + fileName: nil + ) + ), + responseType: Data.self, + requestTimeout: Network.fileUploadTimeout, + using: dependencies + ) + } +} diff --git a/SessionNetworkingKit/FileServer/FileServerAPI.swift b/SessionNetworkingKit/FileServer/FileServerAPI.swift new file mode 100644 index 0000000000..4b3304b3dd --- /dev/null +++ b/SessionNetworkingKit/FileServer/FileServerAPI.swift @@ -0,0 +1,50 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +private typealias FileServer = Network.FileServer +private typealias Endpoint = Network.FileServer.Endpoint + +public extension Network.FileServer { + static func preparedUpload( + data: Data, + requestAndPathBuildTimeout: TimeInterval? = nil, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try Network.PreparedRequest( + request: Request( + endpoint: .file, + destination: .serverUpload( + server: FileServer.fileServer, + x25519PublicKey: FileServer.fileServerPublicKey, + fileName: nil + ), + body: data + ), + responseType: FileUploadResponse.self, + requestTimeout: Network.fileUploadTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) + } + + static func preparedDownload( + url: URL, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try Network.PreparedRequest( + request: Request( + endpoint: .directUrl(url), + destination: .serverDownload( + url: url, + x25519PublicKey: FileServer.fileServerPublicKey, + fileName: nil + ) + ), + responseType: Data.self, + requestTimeout: Network.fileUploadTimeout, + using: dependencies + ) + } +} diff --git a/SessionNetworkingKit/FileServer/FileServerEndpoint.swift b/SessionNetworkingKit/FileServer/FileServerEndpoint.swift new file mode 100644 index 0000000000..5f23b85624 --- /dev/null +++ b/SessionNetworkingKit/FileServer/FileServerEndpoint.swift @@ -0,0 +1,23 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension Network.FileServer { + enum Endpoint: EndpointType { + case file + case fileIndividual(String) + case directUrl(URL) + case sessionVersion + + public static var name: String { "FileServer.Endpoint" } + + public var path: String { + switch self { + case .file: return "file" + case .fileIndividual(let fileId): return "file/\(fileId)" + case .directUrl(let url): return url.path.removingPrefix("/") + case .sessionVersion: return "session_version" + } + } + } +} diff --git a/SessionNetworkingKit/FileServer/Models/AppVersionResponse.swift b/SessionNetworkingKit/FileServer/Models/AppVersionResponse.swift new file mode 100644 index 0000000000..12c1d7fcbb --- /dev/null +++ b/SessionNetworkingKit/FileServer/Models/AppVersionResponse.swift @@ -0,0 +1,99 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension Network.FileServer { + class AppVersionResponse: AppVersionInfo { + enum CodingKeys: String, CodingKey { + case prerelease + } + + public let prerelease: AppVersionInfo? + + public init( + version: String, + updated: TimeInterval?, + name: String?, + notes: String?, + assets: [Asset]?, + prerelease: AppVersionInfo? + ) { + self.prerelease = prerelease + + super.init( + version: version, + updated: updated, + name: name, + notes: notes, + assets: assets + ) + } + + required init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + self.prerelease = try? container.decode(AppVersionInfo?.self, forKey: .prerelease) + + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(prerelease, forKey: .prerelease) + } + } + + // MARK: - AppVersionInfo + + class AppVersionInfo: Codable { + enum CodingKeys: String, CodingKey { + case version = "result" + case updated + case name + case notes + case assets + } + + public struct Asset: Codable { + enum CodingKeys: String, CodingKey { + case name + case url + } + + public let name: String + public let url: String + } + + public let version: String + public let updated: TimeInterval? + public let name: String? + public let notes: String? + public let assets: [Asset]? + + public init( + version: String, + updated: TimeInterval?, + name: String?, + notes: String?, + assets: [Asset]? + ) { + self.version = version + self.updated = updated + self.name = name + self.notes = notes + self.assets = assets + } + + public func encode(to encoder: Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(version, forKey: .version) + try container.encodeIfPresent(updated, forKey: .updated) + try container.encodeIfPresent(name, forKey: .name) + try container.encodeIfPresent(notes, forKey: .notes) + try container.encodeIfPresent(assets, forKey: .assets) + } + } +} diff --git a/SessionSnodeKit/LibSession/LibSession+Networking.swift b/SessionNetworkingKit/LibSession/LibSession+Networking.swift similarity index 98% rename from SessionSnodeKit/LibSession/LibSession+Networking.swift rename to SessionNetworkingKit/LibSession/LibSession+Networking.swift index c445d3c433..53fabef8b7 100644 --- a/SessionSnodeKit/LibSession/LibSession+Networking.swift +++ b/SessionNetworkingKit/LibSession/LibSession+Networking.swift @@ -158,7 +158,7 @@ class LibSessionNetwork: NetworkType { return getSwarm(for: swarmPublicKey) .tryFlatMapWithRandomSnode(retry: retryCount, using: dependencies) { [weak self, dependencies] snode in - try SnodeAPI + try Network.SnodeAPI .preparedGetNetworkTime(from: snode, using: dependencies) .send(using: dependencies) .tryFlatMap { _, timestampMs in @@ -175,7 +175,7 @@ class LibSessionNetwork: NetworkType { ) .map { info, response -> (ResponseInfoType, Data?) in ( - SnodeAPI.LatestTimestampResponseInfo( + Network.SnodeAPI.LatestTimestampResponseInfo( code: info.code, headers: info.headers, timestampMs: timestampMs @@ -188,7 +188,7 @@ class LibSessionNetwork: NetworkType { } } - func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, AppVersionResponse), Error> { + func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, Network.FileServer.AppVersionResponse), Error> { typealias Output = (success: Bool, timeout: Bool, statusCode: Int, headers: [String: String], data: Data?) return dependencies @@ -213,14 +213,14 @@ class LibSessionNetwork: NetworkType { ctx ) } - .tryMap { [dependencies] success, timeout, statusCode, headers, maybeData -> (any ResponseInfoType, AppVersionResponse) in + .tryMap { [dependencies] success, timeout, statusCode, headers, maybeData -> (any ResponseInfoType, Network.FileServer.AppVersionResponse) in try LibSessionNetwork.throwErrorIfNeeded(success, timeout, statusCode, headers, maybeData, using: dependencies) guard let data: Data = maybeData else { throw NetworkError.parsingFailed } return ( Network.ResponseInfo(code: statusCode), - try AppVersionResponse.decoded(from: data, using: dependencies) + try Network.FileServer.AppVersionResponse.decoded(from: data, using: dependencies) ) } .eraseToAnyPublisher() @@ -704,23 +704,27 @@ public extension LibSession { // MARK: - Functions public func suspendNetworkAccess() { - Log.info(.network, "Network access suspended.") isSuspended = true + Log.info(.network, "Network access suspended.") switch network { case .none: break case .some(let network): network_suspend(network) } + + dependencies.notifyAsync(key: .networkLifecycle(.suspended)) } public func resumeNetworkAccess() { isSuspended = false - Log.info(.network, "Network access resumed.") switch network { case .none: break case .some(let network): network_resume(network) } + + Log.info(.network, "Network access resumed.") + dependencies.notifyAsync(key: .networkLifecycle(.resumed)) } public func getOrCreateNetwork() -> AnyPublisher?, Error> { diff --git a/SessionSnodeKit/Meta/Info.plist b/SessionNetworkingKit/Meta/Info.plist similarity index 100% rename from SessionSnodeKit/Meta/Info.plist rename to SessionNetworkingKit/Meta/Info.plist diff --git a/SessionNetworkingKit/Meta/SessionNetworkingKit.h b/SessionNetworkingKit/Meta/SessionNetworkingKit.h new file mode 100644 index 0000000000..dd9ec08864 --- /dev/null +++ b/SessionNetworkingKit/Meta/SessionNetworkingKit.h @@ -0,0 +1,4 @@ +#import + +FOUNDATION_EXPORT double SessionNetworkingKitVersionNumber; +FOUNDATION_EXPORT const unsigned char SessionNetworkingKitVersionString[]; diff --git a/SessionSnodeKit/Models/FileUploadResponse.swift b/SessionNetworkingKit/Models/FileUploadResponse.swift similarity index 98% rename from SessionSnodeKit/Models/FileUploadResponse.swift rename to SessionNetworkingKit/Models/FileUploadResponse.swift index 41ba747b0f..0f7b328d84 100644 --- a/SessionSnodeKit/Models/FileUploadResponse.swift +++ b/SessionNetworkingKit/Models/FileUploadResponse.swift @@ -1,5 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +import Foundation + public struct FileUploadResponse: Codable { public let id: String diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionNetworkingKit/Networking/SnodeAPI.swift similarity index 100% rename from SessionSnodeKit/Networking/SnodeAPI.swift rename to SessionNetworkingKit/Networking/SnodeAPI.swift diff --git a/SessionNetworkingKit/PushNotification/Crypto/Crypto+PushNotification.swift b/SessionNetworkingKit/PushNotification/Crypto/Crypto+PushNotification.swift new file mode 100644 index 0000000000..d0b3158e38 --- /dev/null +++ b/SessionNetworkingKit/PushNotification/Crypto/Crypto+PushNotification.swift @@ -0,0 +1,41 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtil +import SessionUtilitiesKit + +public extension Crypto.Generator { + static func plaintextWithPushNotificationPayload( + payload: Data, + encKey: Data + ) -> Crypto.Generator { + return Crypto.Generator( + id: "plaintextWithPushNotificationPayload", + args: [payload, encKey] + ) { + var cPayload: [UInt8] = Array(payload) + var cEncKey: [UInt8] = Array(encKey) + var maybePlaintext: UnsafeMutablePointer? = nil + var plaintextLen: Int = 0 + + guard + cEncKey.count == 32, + session_decrypt_push_notification( + &cPayload, + cPayload.count, + &cEncKey, + &maybePlaintext, + &plaintextLen + ), + plaintextLen > 0, + let plaintext: Data = maybePlaintext.map({ Data(bytes: $0, count: plaintextLen) }) + else { throw CryptoError.decryptionFailed } + + free(UnsafeMutableRawPointer(mutating: maybePlaintext)) + + return plaintext + } + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/AuthenticatedRequest.swift b/SessionNetworkingKit/PushNotification/Models/AuthenticatedRequest.swift similarity index 97% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/AuthenticatedRequest.swift rename to SessionNetworkingKit/PushNotification/Models/AuthenticatedRequest.swift index 9e7d30447b..6fc0c0ff54 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/AuthenticatedRequest.swift +++ b/SessionNetworkingKit/PushNotification/Models/AuthenticatedRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension PushNotificationAPI { - public class AuthenticatedRequest: Encodable { +extension Network.PushNotification { + class AuthenticatedRequest: Encodable { private enum CodingKeys: String, CodingKey { case pubkey case subaccount diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift b/SessionNetworkingKit/PushNotification/Models/NotificationMetadata.swift similarity index 81% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift rename to SessionNetworkingKit/PushNotification/Models/NotificationMetadata.swift index fefdbad9de..817e19185d 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/NotificationMetadata.swift +++ b/SessionNetworkingKit/PushNotification/Models/NotificationMetadata.swift @@ -1,10 +1,9 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit -extension PushNotificationAPI { - public struct NotificationMetadata: Codable, Equatable { +public extension Network.PushNotification { + struct NotificationMetadata: Codable, Equatable { private enum CodingKeys: String, CodingKey { case accountId = "@" case hash = "#" @@ -22,7 +21,7 @@ extension PushNotificationAPI { public let hash: String /// The swarm namespace in which this message arrived. - public let namespace: SnodeAPI.Namespace + public let namespace: Network.SnodeAPI.Namespace /// The swarm timestamp when the message was created (unix epoch milliseconds) public let createdTimestampMs: Int64 @@ -43,18 +42,19 @@ extension PushNotificationAPI { // MARK: - Decodable -extension PushNotificationAPI.NotificationMetadata { +extension Network.PushNotification.NotificationMetadata { public init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) /// There was a bug at one point where the metadata would include a `null` value for the namespace because we were storing /// messages in a namespace that the storage server didn't have an explicit `namespace_id` for, as a result we need to assume /// that the `namespace` value may not be present in the payload - let namespace: SnodeAPI.Namespace = try container.decodeIfPresent(Int.self, forKey: .namespace) - .map { SnodeAPI.Namespace(rawValue: $0) } + let namespace: Network.SnodeAPI.Namespace = try container + .decodeIfPresent(Int.self, forKey: .namespace) + .map { Network.SnodeAPI.Namespace(rawValue: $0) } .defaulting(to: .unknown) - self = PushNotificationAPI.NotificationMetadata( + self = Network.PushNotification.NotificationMetadata( accountId: try container.decode(String.self, forKey: .accountId), hash: try container.decode(String.self, forKey: .hash), namespace: namespace, @@ -68,9 +68,9 @@ extension PushNotificationAPI.NotificationMetadata { // MARK: - Convenience -public extension PushNotificationAPI.NotificationMetadata { - static var invalid: PushNotificationAPI.NotificationMetadata { - PushNotificationAPI.NotificationMetadata( +public extension Network.PushNotification.NotificationMetadata { + static var invalid: Network.PushNotification.NotificationMetadata { + Network.PushNotification.NotificationMetadata( accountId: "", hash: "", namespace: .unknown, diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift b/SessionNetworkingKit/PushNotification/Models/SubscribeRequest.swift similarity index 96% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift rename to SessionNetworkingKit/PushNotification/Models/SubscribeRequest.swift index 5138d5d8f5..f28f2eb92c 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift +++ b/SessionNetworkingKit/PushNotification/Models/SubscribeRequest.swift @@ -3,10 +3,9 @@ // stringlint:disable import Foundation -import SessionSnodeKit import SessionUtilitiesKit -extension PushNotificationAPI { +public extension Network.PushNotification { struct SubscribeRequest: Encodable { class Subscription: AuthenticatedRequest { private enum CodingKeys: String, CodingKey { @@ -18,7 +17,7 @@ extension PushNotificationAPI { } /// List of integer namespace (-32768 through 32767). These must be sorted in ascending order. - private let namespaces: [SnodeAPI.Namespace] + private let namespaces: [Network.SnodeAPI.Namespace] /// If provided and true then notifications will include the body of the message (as long as it isn't too large); if false then the body will /// not be included in notifications. @@ -68,7 +67,7 @@ extension PushNotificationAPI { // MARK: - Initialization init( - namespaces: [SnodeAPI.Namespace], + namespaces: [Network.SnodeAPI.Namespace], includeMessageData: Bool, serviceInfo: ServiceInfo, notificationsEncryptionKey: Data, @@ -109,9 +108,7 @@ extension PushNotificationAPI { private let subscriptions: [Subscription] - public init( - subscriptions: [Subscription] - ) { + init(subscriptions: [Subscription]) { self.subscriptions = subscriptions } diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeResponse.swift b/SessionNetworkingKit/PushNotification/Models/SubscribeResponse.swift similarity index 85% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeResponse.swift rename to SessionNetworkingKit/PushNotification/Models/SubscribeResponse.swift index bff4193f7d..59c8404399 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeResponse.swift +++ b/SessionNetworkingKit/PushNotification/Models/SubscribeResponse.swift @@ -2,17 +2,17 @@ import Foundation -public extension PushNotificationAPI { +public extension Network.PushNotification { struct SubscribeResponse: Codable { - struct SubResponse: Codable { + public struct SubResponse: Codable { /// Flag indicating the success of the registration - let success: Bool? + public let success: Bool? /// Value is `true` upon an initial registration - let added: Bool? + public let added: Bool? /// Value is `true` upon a renewal/update registration - let updated: Bool? + public let updated: Bool? /// This will be one of the errors found here: /// https://github.com/jagerman/session-push-notification-server/blob/spns-v2/spns/hive/subscription.hpp#L21 @@ -24,13 +24,13 @@ public extension PushNotificationAPI { /// SERVICE_TIMEOUT = 3 // The backend service did not response /// ERROR = 4 // There was some other error processing the subscription (details in the string) /// INTERNAL_ERROR = 5 // An internal program error occured processing the request - let error: Int? + public let error: Int? /// Includes additional information about the error - let message: String? + public let message: String? } - let subResponses: [SubResponse] + public let subResponses: [SubResponse] public init(from decoder: Decoder) throws { guard diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift b/SessionNetworkingKit/PushNotification/Models/UnsubscribeRequest.swift similarity index 98% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift rename to SessionNetworkingKit/PushNotification/Models/UnsubscribeRequest.swift index 1d29c882d8..911632d92b 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift +++ b/SessionNetworkingKit/PushNotification/Models/UnsubscribeRequest.swift @@ -3,10 +3,9 @@ // stringlint:disable import Foundation -import SessionSnodeKit import SessionUtilitiesKit -extension PushNotificationAPI { +extension Network.PushNotification { struct UnsubscribeRequest: Encodable { class Subscription: AuthenticatedRequest { private enum CodingKeys: String, CodingKey { diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeResponse.swift b/SessionNetworkingKit/PushNotification/Models/UnsubscribeResponse.swift similarity index 85% rename from SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeResponse.swift rename to SessionNetworkingKit/PushNotification/Models/UnsubscribeResponse.swift index c89aa19f3a..4e5e0f9e56 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeResponse.swift +++ b/SessionNetworkingKit/PushNotification/Models/UnsubscribeResponse.swift @@ -2,17 +2,17 @@ import Foundation -public extension PushNotificationAPI { +public extension Network.PushNotification { struct UnsubscribeResponse: Codable { - struct SubResponse: Codable { + public struct SubResponse: Codable { /// Flag indicating the success of the registration - let success: Bool? + public let success: Bool? /// Value is `true` upon an initial registration - let added: Bool? + public let added: Bool? /// Value is `true` upon a renewal/update registration - let updated: Bool? + public let updated: Bool? /// This will be one of the errors found here: /// https://github.com/jagerman/session-push-notification-server/blob/spns-v2/spns/hive/subscription.hpp#L21 @@ -24,13 +24,13 @@ public extension PushNotificationAPI { /// SERVICE_TIMEOUT = 3 // The backend service did not response /// ERROR = 4 // There was some other error processing the subscription (details in the string) /// INTERNAL_ERROR = 5 // An internal program error occured processing the request - let error: Int? + public let error: Int? /// Includes additional information about the error - let message: String? + public let message: String? } - let subResponses: [SubResponse] + public let subResponses: [SubResponse] public init(from decoder: Decoder) throws { guard diff --git a/SessionNetworkingKit/PushNotification/PushNotification.swift b/SessionNetworkingKit/PushNotification/PushNotification.swift new file mode 100644 index 0000000000..b080f7154e --- /dev/null +++ b/SessionNetworkingKit/PushNotification/PushNotification.swift @@ -0,0 +1,29 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtilitiesKit + +// MARK: - KeychainStorage + +public extension KeychainStorage.DataKey { static let pushNotificationEncryptionKey: Self = "PNEncryptionKeyKey" } + +// MARK: - Log.Category + +public extension Log.Category { + static let pushNotificationAPI: Log.Category = .create("PushNotificationAPI", defaultLevel: .info) +} + +// MARK: - Network.PushNotification + +public extension Network { + enum PushNotification { + internal static let encryptionKeyLength: Int = 32 + internal static let maxRetryCount: Int = 4 + public static let tokenExpirationInterval: TimeInterval = (12 * 60 * 60) + + internal static let server: String = "https://push.getsession.org" + internal static let serverPublicKey = "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b" + } +} diff --git a/SessionNetworkingKit/PushNotification/PushNotificationAPI.swift b/SessionNetworkingKit/PushNotification/PushNotificationAPI.swift new file mode 100644 index 0000000000..dfc2cbee59 --- /dev/null +++ b/SessionNetworkingKit/PushNotification/PushNotificationAPI.swift @@ -0,0 +1,194 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import Combine +import UserNotifications +import SessionUtilitiesKit + +public extension Network.PushNotification { + static func preparedSubscribe( + token: Data, + swarms: [(sessionId: SessionId, authMethod: AuthenticationMethod)], + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + guard dependencies[defaults: .standard, key: .isUsingFullAPNs] else { + throw NetworkError.invalidPreparedRequest + } + + guard let notificationsEncryptionKey: Data = try? dependencies[singleton: .keychain].getOrGenerateEncryptionKey( + forKey: .pushNotificationEncryptionKey, + length: encryptionKeyLength, + cat: .pushNotificationAPI, + legacyKey: "PNEncryptionKeyKey", + legacyService: "PNKeyChainService" + ) else { + Log.error(.pushNotificationAPI, "Unable to retrieve PN encryption key.") + throw KeychainStorageError.keySpecInvalid + } + + return try Network.PreparedRequest( + request: Request( + method: .post, + endpoint: Endpoint.subscribe, + body: SubscribeRequest( + subscriptions: swarms.map { sessionId, authMethod -> SubscribeRequest.Subscription in + SubscribeRequest.Subscription( + namespaces: { + switch sessionId.prefix { + case .group: return [ + .groupMessages, + .configGroupKeys, + .configGroupInfo, + .configGroupMembers, + .revokedRetrievableGroupMessages + ] + default: return [ + .default, + .configUserProfile, + .configContacts, + .configConvoInfoVolatile, + .configUserGroups + ] + } + }(), + /// Note: Unfortunately we always need the message content because without the content + /// control messages can't be distinguished from visible messages which results in the + /// 'generic' notification being shown when receiving things like typing indicator updates + includeMessageData: true, + serviceInfo: ServiceInfo( + token: token.toHexString() + ), + notificationsEncryptionKey: notificationsEncryptionKey, + authMethod: authMethod, + timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000) // Seconds + ) + } + ) + ), + responseType: SubscribeResponse.self, + retryCount: Network.PushNotification.maxRetryCount, + using: dependencies + ) + .handleEvents( + receiveOutput: { _, response in + zip(response.subResponses, swarms).forEach { subResponse, swarm in + guard subResponse.success != true else { return } + + Log.error(.pushNotificationAPI, "Couldn't subscribe for push notifications for: \(swarm.sessionId) due to error (\(subResponse.error ?? -1)): \(subResponse.message ?? "nil").") + } + }, + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): Log.error(.pushNotificationAPI, "Couldn't subscribe for push notifications due to error: \(error).") + } + } + ) + } + + static func preparedUnsubscribe( + token: Data, + swarms: [(sessionId: SessionId, authMethod: AuthenticationMethod)], + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try Network.PreparedRequest( + request: Request( + method: .post, + endpoint: Endpoint.unsubscribe, + body: UnsubscribeRequest( + subscriptions: swarms.map { sessionId, authMethod -> UnsubscribeRequest.Subscription in + UnsubscribeRequest.Subscription( + serviceInfo: ServiceInfo( + token: token.toHexString() + ), + authMethod: authMethod, + timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000) // Seconds + ) + } + ) + ), + responseType: UnsubscribeResponse.self, + retryCount: Network.PushNotification.maxRetryCount, + using: dependencies + ) + .handleEvents( + receiveOutput: { _, response in + zip(response.subResponses, swarms).forEach { subResponse, swarm in + guard subResponse.success != true else { return } + + Log.error(.pushNotificationAPI, "Couldn't unsubscribe for push notifications for: \(swarm.sessionId) due to error (\(subResponse.error ?? -1)): \(subResponse.message ?? "nil").") + } + }, + receiveCompletion: { result in + switch result { + case .finished: break + case .failure(let error): Log.error(.pushNotificationAPI, "Couldn't unsubscribe for push notifications due to error: \(error).") + } + } + ) + } + + // MARK: - Notification Handling + + static func processNotification( + notificationContent: UNNotificationContent, + using dependencies: Dependencies + ) -> (data: Data?, metadata: NotificationMetadata, result: ProcessResult) { + // Make sure the notification is from the updated push server + guard notificationContent.userInfo["spns"] != nil else { + return (nil, .invalid, .legacyFailure) + } + + guard let base64EncodedEncString: String = notificationContent.userInfo["enc_payload"] as? String else { + return (nil, .invalid, .failureNoContent) + } + + // Decrypt and decode the payload + let notification: BencodeResponse + + do { + guard let encryptedData: Data = Data(base64Encoded: base64EncodedEncString) else { + throw CryptoError.invalidBase64EncodedData + } + + let notificationsEncryptionKey: Data = try dependencies[singleton: .keychain].getOrGenerateEncryptionKey( + forKey: .pushNotificationEncryptionKey, + length: encryptionKeyLength, + cat: .pushNotificationAPI, + legacyKey: "PNEncryptionKeyKey", + legacyService: "PNKeyChainService" + ) + let decryptedData: Data = try dependencies[singleton: .crypto].tryGenerate( + .plaintextWithPushNotificationPayload( + payload: encryptedData, + encKey: notificationsEncryptionKey + ) + ) + notification = try BencodeDecoder(using: dependencies) + .decode(BencodeResponse.self, from: decryptedData) + } + catch { + Log.error(.pushNotificationAPI, "Failed to decrypt or decode notification due to error: \(error)") + return (nil, .invalid, .failure) + } + + // If the metadata says that the message was too large then we should show the generic + // notification (this is a valid case) + guard !notification.info.dataTooLong else { return (nil, notification.info, .successTooLong) } + + // Check that the body we were given is valid and not empty + guard + let notificationData: Data = notification.data, + notification.info.dataLength == notificationData.count, + !notificationData.isEmpty + else { + Log.error(.pushNotificationAPI, "Get notification data failed") + return (nil, notification.info, .failureNoContent) + } + + // Success, we have the notification content + return (notificationData, notification.info, .success) + } +} diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift b/SessionNetworkingKit/PushNotification/PushNotificationEndpoint.swift similarity index 75% rename from SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift rename to SessionNetworkingKit/PushNotification/PushNotificationEndpoint.swift index 36ed02e3e2..5fdb987b04 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/PushNotificationAPIEndpoint.swift +++ b/SessionNetworkingKit/PushNotification/PushNotificationEndpoint.swift @@ -3,15 +3,15 @@ // stringlint:disable import Foundation -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit -public extension PushNotificationAPI { +public extension Network.PushNotification { enum Endpoint: EndpointType { case subscribe case unsubscribe - public static var name: String { "PushNotificationAPI.Endpoint" } + public static var name: String { "PushNotification.Endpoint" } public var path: String { switch self { diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/ProcessResult.swift b/SessionNetworkingKit/PushNotification/Types/ProcessResult.swift similarity index 84% rename from SessionMessagingKit/Sending & Receiving/Notifications/Types/ProcessResult.swift rename to SessionNetworkingKit/PushNotification/Types/ProcessResult.swift index 07496de265..a33e84f6c0 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/ProcessResult.swift +++ b/SessionNetworkingKit/PushNotification/Types/ProcessResult.swift @@ -2,7 +2,7 @@ import Foundation -public extension PushNotificationAPI { +public extension Network.PushNotification { enum ProcessResult { case success case successTooLong diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Request+PushNotificationAPI.swift b/SessionNetworkingKit/PushNotification/Types/Request+PushNotificationAPI.swift similarity index 69% rename from SessionMessagingKit/Sending & Receiving/Notifications/Types/Request+PushNotificationAPI.swift rename to SessionNetworkingKit/PushNotification/Types/Request+PushNotificationAPI.swift index 78000ad2ce..480c90bf65 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Request+PushNotificationAPI.swift +++ b/SessionNetworkingKit/PushNotification/Types/Request+PushNotificationAPI.swift @@ -1,12 +1,9 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit import SessionUtilitiesKit -// MARK: Request - PushNotificationAPI - -public extension Request where Endpoint == PushNotificationAPI.Endpoint { +public extension Request where Endpoint == Network.PushNotification.Endpoint { init( method: HTTPMethod, endpoint: Endpoint, @@ -18,10 +15,10 @@ public extension Request where Endpoint == PushNotificationAPI.Endpoint { endpoint: endpoint, destination: try .server( method: method, - server: PushNotificationAPI.server, + server: Network.PushNotification.server, queryParameters: queryParameters, headers: headers, - x25519PublicKey: PushNotificationAPI.serverPublicKey + x25519PublicKey: Network.PushNotification.serverPublicKey ), body: body ) diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift b/SessionNetworkingKit/PushNotification/Types/Service.swift similarity index 84% rename from SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift rename to SessionNetworkingKit/PushNotification/Types/Service.swift index c930c0a176..fa33fe3cde 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/Service.swift +++ b/SessionNetworkingKit/PushNotification/Types/Service.swift @@ -8,15 +8,15 @@ import SessionUtilitiesKit // MARK: - FeatureStorage public extension FeatureStorage { - static let pushNotificationService: FeatureConfig = Dependencies.create( + static let pushNotificationService: FeatureConfig = Dependencies.create( identifier: "pushNotificationService", defaultOption: .apns ) } -// MARK: - PushNotificationAPI.Service +// MARK: - Network.PushNotification.Service -public extension PushNotificationAPI { +public extension Network.PushNotification { enum Service: String, Codable, CaseIterable, FeatureOption { case apns case sandbox = "apns-sandbox" // Use for push notifications in Testnet diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/Types/ServiceInfo.swift b/SessionNetworkingKit/PushNotification/Types/ServiceInfo.swift similarity index 91% rename from SessionMessagingKit/Sending & Receiving/Notifications/Types/ServiceInfo.swift rename to SessionNetworkingKit/PushNotification/Types/ServiceInfo.swift index 8b1ccfeaed..b534816f35 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/Types/ServiceInfo.swift +++ b/SessionNetworkingKit/PushNotification/Types/ServiceInfo.swift @@ -2,7 +2,7 @@ import Foundation -extension PushNotificationAPI { +extension Network.PushNotification { struct ServiceInfo: Codable { private enum CodingKeys: String, CodingKey { case token diff --git a/SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroupAPI.swift b/SessionNetworkingKit/SOGS/Crypto/Crypto+SOGS.swift similarity index 60% rename from SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroupAPI.swift rename to SessionNetworkingKit/SOGS/Crypto/Crypto+SOGS.swift index 516fb0a6ac..9b48b1c7f4 100644 --- a/SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroupAPI.swift +++ b/SessionNetworkingKit/SOGS/Crypto/Crypto+SOGS.swift @@ -3,7 +3,6 @@ // stringlint:disable import Foundation -import CryptoKit import SessionUtil import SessionUtilitiesKit @@ -152,90 +151,3 @@ public extension Crypto.Verification { } } } - -// MARK: - Messages - -public extension Crypto.Generator { - static func ciphertextWithSessionBlindingProtocol( - plaintext: Data, - recipientBlindedId: String, - serverPublicKey: String - ) -> Crypto.Generator { - return Crypto.Generator( - id: "ciphertextWithSessionBlindingProtocol", - args: [plaintext, serverPublicKey] - ) { dependencies in - var cPlaintext: [UInt8] = Array(plaintext) - var cEd25519SecretKey: [UInt8] = dependencies[cache: .general].ed25519SecretKey - var cRecipientBlindedId: [UInt8] = Array(Data(hex: recipientBlindedId)) - var cServerPublicKey: [UInt8] = Array(Data(hex: serverPublicKey)) - var maybeCiphertext: UnsafeMutablePointer? = nil - var ciphertextLen: Int = 0 - - guard !cEd25519SecretKey.isEmpty else { throw MessageSenderError.noUserED25519KeyPair } - guard - cEd25519SecretKey.count == 64, - cServerPublicKey.count == 32, - session_encrypt_for_blinded_recipient( - &cPlaintext, - cPlaintext.count, - &cEd25519SecretKey, - &cServerPublicKey, - &cRecipientBlindedId, - &maybeCiphertext, - &ciphertextLen - ), - ciphertextLen > 0, - let ciphertext: Data = maybeCiphertext.map({ Data(bytes: $0, count: ciphertextLen) }) - else { throw MessageSenderError.encryptionFailed } - - free(UnsafeMutableRawPointer(mutating: maybeCiphertext)) - - return ciphertext - } - } - - static func plaintextWithSessionBlindingProtocol( - ciphertext: Data, - senderId: String, - recipientId: String, - serverPublicKey: String - ) -> Crypto.Generator<(plaintext: Data, senderSessionIdHex: String)> { - return Crypto.Generator( - id: "plaintextWithSessionBlindingProtocol", - args: [ciphertext, senderId, recipientId] - ) { dependencies in - var cCiphertext: [UInt8] = Array(ciphertext) - var cEd25519SecretKey: [UInt8] = dependencies[cache: .general].ed25519SecretKey - var cSenderId: [UInt8] = Array(Data(hex: senderId)) - var cRecipientId: [UInt8] = Array(Data(hex: recipientId)) - var cServerPublicKey: [UInt8] = Array(Data(hex: serverPublicKey)) - var cSenderSessionId: [CChar] = [CChar](repeating: 0, count: 67) - var maybePlaintext: UnsafeMutablePointer? = nil - var plaintextLen: Int = 0 - - guard !cEd25519SecretKey.isEmpty else { throw MessageSenderError.noUserED25519KeyPair } - guard - cEd25519SecretKey.count == 64, - cServerPublicKey.count == 32, - session_decrypt_for_blinded_recipient( - &cCiphertext, - cCiphertext.count, - &cEd25519SecretKey, - &cServerPublicKey, - &cSenderId, - &cRecipientId, - &cSenderSessionId, - &maybePlaintext, - &plaintextLen - ), - plaintextLen > 0, - let plaintext: Data = maybePlaintext.map({ Data(bytes: $0, count: plaintextLen) }) - else { throw MessageReceiverError.decryptionFailed } - - free(UnsafeMutableRawPointer(mutating: maybePlaintext)) - - return (plaintext, String(cString: cSenderSessionId)) - } - } -} diff --git a/SessionNetworkingKit/SOGS/Models/CapabilitiesResponse.swift b/SessionNetworkingKit/SOGS/Models/CapabilitiesResponse.swift new file mode 100644 index 0000000000..1e5ddf28c1 --- /dev/null +++ b/SessionNetworkingKit/SOGS/Models/CapabilitiesResponse.swift @@ -0,0 +1,74 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SOGS { + public struct CapabilitiesResponse: Codable, Equatable { + public let capabilities: [String] + public let missing: [String]? + + // MARK: - Initialization + + public init(capabilities: [String], missing: [String]? = nil) { + self.capabilities = capabilities + self.missing = missing + } + } +} + +//public struct Capability: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { +// public static var databaseTableName: String { "capability" } +// +// public typealias Columns = CodingKeys +// public enum CodingKeys: String, CodingKey, ColumnExpression { +// case openGroupServer +// case variant +// case isMissing +// } +// +// public enum Variant: Equatable, Hashable, CaseIterable, Codable, DatabaseValueConvertible { +// public static var allCases: [Variant] { +// [.sogs, .blind, .reactions] +// } +// +// case sogs +// case blind +// case reactions +// +// /// Fallback case if the capability isn't supported by this version of the app +// case unsupported(String) +// +// // MARK: - Convenience +// +// public var rawValue: String { +// switch self { +// case .unsupported(let originalValue): return originalValue +// default: return "\(self)" +// } +// } +// +// // MARK: - Initialization +// +// public init(from valueString: String) { +// let maybeValue: Variant? = Variant.allCases.first { $0.rawValue == valueString } +// +// self = (maybeValue ?? .unsupported(valueString)) +// } +// } +// +// public let openGroupServer: String +// public let variant: Variant +// public let isMissing: Bool +// +// // MARK: - Initialization +// +// public init( +// openGroupServer: String, +// variant: Variant, +// isMissing: Bool +// ) { +// self.openGroupServer = openGroupServer +// self.variant = variant +// self.isMissing = isMissing +// } +//} diff --git a/SessionMessagingKit/Open Groups/Models/DeleteInboxResponse.swift b/SessionNetworkingKit/SOGS/Models/DeleteInboxResponse.swift similarity index 86% rename from SessionMessagingKit/Open Groups/Models/DeleteInboxResponse.swift rename to SessionNetworkingKit/SOGS/Models/DeleteInboxResponse.swift index fa67ea9351..c5b82413bf 100644 --- a/SessionMessagingKit/Open Groups/Models/DeleteInboxResponse.swift +++ b/SessionNetworkingKit/SOGS/Models/DeleteInboxResponse.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { public struct DeleteInboxResponse: Codable { let deleted: UInt64 } diff --git a/SessionMessagingKit/Open Groups/Models/DirectMessage.swift b/SessionNetworkingKit/SOGS/Models/DirectMessage.swift similarity index 97% rename from SessionMessagingKit/Open Groups/Models/DirectMessage.swift rename to SessionNetworkingKit/SOGS/Models/DirectMessage.swift index f2e7421eff..507eb5c576 100644 --- a/SessionMessagingKit/Open Groups/Models/DirectMessage.swift +++ b/SessionNetworkingKit/SOGS/Models/DirectMessage.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { public struct DirectMessage: Codable { enum CodingKeys: String, CodingKey { case id diff --git a/SessionMessagingKit/Open Groups/Models/PinnedMessage.swift b/SessionNetworkingKit/SOGS/Models/PinnedMessage.swift similarity index 96% rename from SessionMessagingKit/Open Groups/Models/PinnedMessage.swift rename to SessionNetworkingKit/SOGS/Models/PinnedMessage.swift index e8f1f7a8e2..332a8bb34a 100644 --- a/SessionMessagingKit/Open Groups/Models/PinnedMessage.swift +++ b/SessionNetworkingKit/SOGS/Models/PinnedMessage.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { public struct PinnedMessage: Codable, Equatable { enum CodingKeys: String, CodingKey { case id diff --git a/SessionMessagingKit/Open Groups/Models/ReactionResponse.swift b/SessionNetworkingKit/SOGS/Models/ReactionResponse.swift similarity index 98% rename from SessionMessagingKit/Open Groups/Models/ReactionResponse.swift rename to SessionNetworkingKit/SOGS/Models/ReactionResponse.swift index cfded186d2..6e4992d688 100644 --- a/SessionMessagingKit/Open Groups/Models/ReactionResponse.swift +++ b/SessionNetworkingKit/SOGS/Models/ReactionResponse.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { public struct ReactionAddResponse: Codable, Equatable { enum CodingKeys: String, CodingKey { case added diff --git a/SessionMessagingKit/Open Groups/Models/Room.swift b/SessionNetworkingKit/SOGS/Models/Room.swift similarity index 98% rename from SessionMessagingKit/Open Groups/Models/Room.swift rename to SessionNetworkingKit/SOGS/Models/Room.swift index f1a2f32d6f..6f188c5ce7 100644 --- a/SessionMessagingKit/Open Groups/Models/Room.swift +++ b/SessionNetworkingKit/SOGS/Models/Room.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { public struct Room: Codable, Equatable { enum CodingKeys: String, CodingKey { case token @@ -146,7 +146,7 @@ extension OpenGroupAPI { // MARK: - Decoding -extension OpenGroupAPI.Room { +extension Network.SOGS.Room { public init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) @@ -156,7 +156,7 @@ extension OpenGroupAPI.Room { (try? container.decode(String.self, forKey: .imageId)) ) - self = OpenGroupAPI.Room( + self = Network.SOGS.Room( token: try container.decode(String.self, forKey: .token), name: try container.decode(String.self, forKey: .name), roomDescription: try? container.decode(String.self, forKey: .roomDescription), @@ -167,7 +167,7 @@ extension OpenGroupAPI.Room { activeUsers: try container.decode(Int64.self, forKey: .activeUsers), activeUsersCutoff: try container.decode(Int64.self, forKey: .activeUsersCutoff), imageId: maybeImageId, - pinnedMessages: try? container.decode([OpenGroupAPI.PinnedMessage].self, forKey: .pinnedMessages), + pinnedMessages: try? container.decode([Network.SOGS.PinnedMessage].self, forKey: .pinnedMessages), admin: ((try? container.decode(Bool.self, forKey: .admin)) ?? false), globalAdmin: ((try? container.decode(Bool.self, forKey: .globalAdmin)) ?? false), diff --git a/SessionMessagingKit/Open Groups/Models/RoomPollInfo.swift b/SessionNetworkingKit/SOGS/Models/RoomPollInfo.swift similarity index 95% rename from SessionMessagingKit/Open Groups/Models/RoomPollInfo.swift rename to SessionNetworkingKit/SOGS/Models/RoomPollInfo.swift index de43a68a62..a2b87f424d 100644 --- a/SessionMessagingKit/Open Groups/Models/RoomPollInfo.swift +++ b/SessionNetworkingKit/SOGS/Models/RoomPollInfo.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { /// This only contains ephemeral data public struct RoomPollInfo: Codable { enum CodingKeys: String, CodingKey { @@ -92,8 +92,8 @@ extension OpenGroupAPI { // MARK: - Convenience -extension OpenGroupAPI.RoomPollInfo { - init(room: OpenGroupAPI.Room) { +public extension Network.SOGS.RoomPollInfo { + init(room: Network.SOGS.Room) { self.init( token: room.token, activeUsers: room.activeUsers, @@ -115,11 +115,11 @@ extension OpenGroupAPI.RoomPollInfo { // MARK: - Decoding -extension OpenGroupAPI.RoomPollInfo { +extension Network.SOGS.RoomPollInfo { public init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - self = OpenGroupAPI.RoomPollInfo( + self = Network.SOGS.RoomPollInfo( token: try container.decode(String.self, forKey: .token), activeUsers: try container.decode(Int64.self, forKey: .activeUsers), @@ -137,7 +137,7 @@ extension OpenGroupAPI.RoomPollInfo { upload: try container.decode(Bool.self, forKey: .upload), defaultUpload: try? container.decode(Bool.self, forKey: .defaultUpload), - details: try? container.decode(OpenGroupAPI.Room.self, forKey: .details) + details: try? container.decode(Network.SOGS.Room.self, forKey: .details) ) } } diff --git a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift b/SessionNetworkingKit/SOGS/Models/SOGSMessage.swift similarity index 96% rename from SessionMessagingKit/Open Groups/Models/SOGSMessage.swift rename to SessionNetworkingKit/SOGS/Models/SOGSMessage.swift index ef9aa97060..5b902a0941 100644 --- a/SessionMessagingKit/Open Groups/Models/SOGSMessage.swift +++ b/SessionNetworkingKit/SOGS/Models/SOGSMessage.swift @@ -1,10 +1,9 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit import SessionUtilitiesKit -extension OpenGroupAPI { +extension Network.SOGS { public struct Message: Codable, Equatable { enum CodingKeys: String, CodingKey { case id @@ -56,7 +55,7 @@ extension OpenGroupAPI { // MARK: - Decoder -extension OpenGroupAPI.Message { +extension Network.SOGS.Message { public init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) @@ -102,7 +101,7 @@ extension OpenGroupAPI.Message { } } - self = OpenGroupAPI.Message( + self = Network.SOGS.Message( id: try container.decode(Int64.self, forKey: .id), sender: try container.decodeIfPresent(String.self, forKey: .sender), posted: try container.decodeIfPresent(TimeInterval.self, forKey: .posted), @@ -119,11 +118,11 @@ extension OpenGroupAPI.Message { } } -extension OpenGroupAPI.Message.Reaction { +extension Network.SOGS.Message.Reaction { public init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - self = OpenGroupAPI.Message.Reaction( + self = Network.SOGS.Message.Reaction( count: try container.decode(Int64.self, forKey: .count), reactors: try container.decodeIfPresent([String].self, forKey: .reactors), you: ((try container.decodeIfPresent(Bool.self, forKey: .you)) ?? false), diff --git a/SessionMessagingKit/Open Groups/Models/SendDirectMessageRequest.swift b/SessionNetworkingKit/SOGS/Models/SendDirectMessageRequest.swift similarity index 95% rename from SessionMessagingKit/Open Groups/Models/SendDirectMessageRequest.swift rename to SessionNetworkingKit/SOGS/Models/SendDirectMessageRequest.swift index 19df350f9e..4a72a11423 100644 --- a/SessionMessagingKit/Open Groups/Models/SendDirectMessageRequest.swift +++ b/SessionNetworkingKit/SOGS/Models/SendDirectMessageRequest.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { public struct SendDirectMessageRequest: Codable { let message: Data diff --git a/SessionMessagingKit/Open Groups/Models/SendDirectMessageResponse.swift b/SessionNetworkingKit/SOGS/Models/SendDirectMessageResponse.swift similarity index 90% rename from SessionMessagingKit/Open Groups/Models/SendDirectMessageResponse.swift rename to SessionNetworkingKit/SOGS/Models/SendDirectMessageResponse.swift index a8e998f8ac..a076f4edd4 100644 --- a/SessionMessagingKit/Open Groups/Models/SendDirectMessageResponse.swift +++ b/SessionNetworkingKit/SOGS/Models/SendDirectMessageResponse.swift @@ -2,8 +2,8 @@ import Foundation -extension OpenGroupAPI { - public struct SendDirectMessageResponse: Codable, Equatable { +public extension Network.SOGS { + struct SendDirectMessageResponse: Codable, Equatable { enum CodingKeys: String, CodingKey { case id case sender diff --git a/SessionMessagingKit/Open Groups/Models/SendMessageRequest.swift b/SessionNetworkingKit/SOGS/Models/SendSOGSMessageRequest.swift similarity index 97% rename from SessionMessagingKit/Open Groups/Models/SendMessageRequest.swift rename to SessionNetworkingKit/SOGS/Models/SendSOGSMessageRequest.swift index 98007184aa..b6520ad7cd 100644 --- a/SessionMessagingKit/Open Groups/Models/SendMessageRequest.swift +++ b/SessionNetworkingKit/SOGS/Models/SendSOGSMessageRequest.swift @@ -2,8 +2,8 @@ import Foundation -extension OpenGroupAPI { - public struct SendMessageRequest: Codable { +extension Network.SOGS { + public struct SendSOGSMessageRequest: Codable { enum CodingKeys: String, CodingKey { case data case signature diff --git a/SessionMessagingKit/Open Groups/Models/UpdateMessageRequest.swift b/SessionNetworkingKit/SOGS/Models/UpdateMessageRequest.swift similarity index 98% rename from SessionMessagingKit/Open Groups/Models/UpdateMessageRequest.swift rename to SessionNetworkingKit/SOGS/Models/UpdateMessageRequest.swift index f18f72a633..640a5d92d8 100644 --- a/SessionMessagingKit/Open Groups/Models/UpdateMessageRequest.swift +++ b/SessionNetworkingKit/SOGS/Models/UpdateMessageRequest.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { public struct UpdateMessageRequest: Codable { /// The serialized message body (encoded in base64 when encoding) let data: Data diff --git a/SessionMessagingKit/Open Groups/Models/UserBanRequest.swift b/SessionNetworkingKit/SOGS/Models/UserBanRequest.swift similarity index 98% rename from SessionMessagingKit/Open Groups/Models/UserBanRequest.swift rename to SessionNetworkingKit/SOGS/Models/UserBanRequest.swift index caff1a17de..249bf8db0c 100644 --- a/SessionMessagingKit/Open Groups/Models/UserBanRequest.swift +++ b/SessionNetworkingKit/SOGS/Models/UserBanRequest.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { struct UserBanRequest: Codable { /// List of one or more room tokens from which the user should be banned (the invoking user must be a `moderator` /// of all of the given rooms diff --git a/SessionMessagingKit/Open Groups/Models/UserModeratorRequest.swift b/SessionNetworkingKit/SOGS/Models/UserModeratorRequest.swift similarity index 99% rename from SessionMessagingKit/Open Groups/Models/UserModeratorRequest.swift rename to SessionNetworkingKit/SOGS/Models/UserModeratorRequest.swift index ece21d2baa..8151ede9ee 100644 --- a/SessionMessagingKit/Open Groups/Models/UserModeratorRequest.swift +++ b/SessionNetworkingKit/SOGS/Models/UserModeratorRequest.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { struct UserModeratorRequest: Codable { /// List of room tokens to which the moderator status should be applied. The invoking user must be an admin of all of the given rooms. /// diff --git a/SessionMessagingKit/Open Groups/Models/UserUnbanRequest.swift b/SessionNetworkingKit/SOGS/Models/UserUnbanRequest.swift similarity index 96% rename from SessionMessagingKit/Open Groups/Models/UserUnbanRequest.swift rename to SessionNetworkingKit/SOGS/Models/UserUnbanRequest.swift index b0e8a2ab99..d1524d3e4a 100644 --- a/SessionMessagingKit/Open Groups/Models/UserUnbanRequest.swift +++ b/SessionNetworkingKit/SOGS/Models/UserUnbanRequest.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { struct UserUnbanRequest: Codable { /// List of one or more room tokens from which the user should be banned (the invoking user must be a `moderator` /// of all of the given rooms diff --git a/SessionNetworkingKit/SOGS/SOGS.swift b/SessionNetworkingKit/SOGS/SOGS.swift new file mode 100644 index 0000000000..0c5e5ce75e --- /dev/null +++ b/SessionNetworkingKit/SOGS/SOGS.swift @@ -0,0 +1,15 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension Network { + enum SOGS { + public static let legacyDefaultServerIP = "116.203.70.33" + public static let defaultServer = "https://open.getsession.org" + public static let defaultServerPublicKey = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238" + public static let validTimestampVarianceThreshold: TimeInterval = (6 * 60 * 60) + internal static let maxInactivityPeriodForPolling: TimeInterval = (14 * 24 * 60 * 60) + + public static let workQueue = DispatchQueue(label: "SOGS.workQueue", qos: .userInitiated) // It's important that this is a serial queue + } +} diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionNetworkingKit/SOGS/SOGSAPI.swift similarity index 88% rename from SessionMessagingKit/Open Groups/OpenGroupAPI.swift rename to SessionNetworkingKit/SOGS/SOGSAPI.swift index 3d0bc3ccb2..981058ae32 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionNetworkingKit/SOGS/SOGSAPI.swift @@ -3,25 +3,15 @@ // stringlint:disable import Foundation -import SessionSnodeKit import SessionUtilitiesKit -public enum OpenGroupAPI { - public struct RoomInfo: Codable { +public extension Network.SOGS { + struct PollRoomInfo: Codable { let roomToken: String let infoUpdates: Int64 let sequenceNumber: Int64 } - // MARK: - Settings - - public static let legacyDefaultServerIP = "116.203.70.33" - public static let defaultServer = "https://open.getsession.org" - public static let defaultServerPublicKey = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238" - public static let validTimestampVarianceThreshold: TimeInterval = (6 * 60 * 60) - - public static let workQueue = DispatchQueue(label: "OpenGroupAPI.workQueue", qos: .userInitiated) // It's important that this is a serial queue - // MARK: - Batching & Polling /// This is a convenience method which calls `/batch` with a pre-defined set of requests used to update an Open @@ -32,10 +22,11 @@ public enum OpenGroupAPI { /// - Messages (includes additions and deletions) /// - Inbox for the server /// - Outbox for the server - public static func preparedPoll( - roomInfo: [RoomInfo], + static func preparedPoll( + roomInfo: [PollRoomInfo], lastInboxMessageId: Int64, lastOutboxMessageId: Int64, + checkForCommunityMessageRequests: Bool, hasPerformedInitialPoll: Bool, timeSinceLastPoll: TimeInterval, authMethod: AuthenticationMethod, @@ -60,7 +51,7 @@ public enum OpenGroupAPI { // 'maxInactivityPeriod' then just retrieve recent messages instead // of trying to get all messages since the last one retrieved !hasPerformedInitialPoll && - timeSinceLastPoll > CommunityPoller.maxInactivityPeriod + timeSinceLastPoll > maxInactivityPeriodForPolling ) ) @@ -93,7 +84,7 @@ public enum OpenGroupAPI { !supportsBlinding ? [] : [ // Inbox (only check the inbox if the user want's community message requests) - (!dependencies.mutate(cache: .libSession) { $0.get(.checkForCommunityMessageRequests) } ? nil : + (!checkForCommunityMessageRequests ? nil : (lastInboxMessageId == 0 ? try preparedInbox(authMethod: authMethod, using: dependencies) : try preparedInboxSince( @@ -117,13 +108,13 @@ public enum OpenGroupAPI { ) ) - return try OpenGroupAPI + return try Network.SOGS .preparedBatch( requests: preparedRequests, authMethod: authMethod, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Submits multiple requests wrapped up in a single request, runs them all, then returns the result of each one @@ -133,7 +124,7 @@ public enum OpenGroupAPI { /// /// For contained subrequests that specify a body (i.e. POST or PUT requests) exactly one of `json`, `b64`, or `bytes` must be provided /// with the request body. - public static func preparedBatch( + static func preparedBatch( requests: [any ErasedPreparedRequest], authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -149,7 +140,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// This is like `/batch`, except that it guarantees to perform requests sequentially in the order provided and will stop processing requests @@ -165,9 +156,10 @@ public enum OpenGroupAPI { private static func preparedSequence( requests: [any ErasedPreparedRequest], authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest> { - return try Network.PreparedRequest( + let preparedRequest = try Network.PreparedRequest( request: Request( method: .post, endpoint: Endpoint.sequence, @@ -178,7 +170,11 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + + return (skipAuthentication ? + preparedRequest : + try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) + ) } // MARK: - Capabilities @@ -190,20 +186,25 @@ public enum OpenGroupAPI { /// /// Eg. `GET /capabilities` could return `{"capabilities": ["sogs", "batch"]}` `GET /capabilities?required=magic,batch` /// could return: `{"capabilities": ["sogs", "batch"], "missing": ["magic"]}` - public static func preparedCapabilities( + static func preparedCapabilities( authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( + ) throws -> Network.PreparedRequest { + let preparedRequest = try Network.PreparedRequest( request: Request( endpoint: .capabilities, authMethod: authMethod ), - responseType: Capabilities.self, + responseType: CapabilitiesResponse.self, additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + + return (skipAuthentication ? + preparedRequest : + try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) + ) } // MARK: - Room @@ -211,11 +212,12 @@ public enum OpenGroupAPI { /// Returns a list of available rooms on the server /// /// Rooms to which the user does not have access (e.g. because they are banned, or the room has restricted access permissions) are not included - public static func preparedRooms( + static func preparedRooms( authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest<[Room]> { - return try Network.PreparedRequest( + let preparedRequest = try Network.PreparedRequest( request: Request( endpoint: .rooms, authMethod: authMethod @@ -224,11 +226,15 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + + return (skipAuthentication ? + preparedRequest : + try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) + ) } /// Returns the details of a single room - public static func preparedRoom( + static func preparedRoom( roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -242,14 +248,14 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Polls a room for metadata updates /// /// The endpoint polls room metadata for this room, always including the instantaneous room details (such as the user's permission and current /// number of active users), and including the full room metadata if the room's info_updated counter has changed from the provided value - public static func preparedRoomPollInfo( + static func preparedRoomPollInfo( lastUpdated: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -264,22 +270,22 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } - public typealias CapabilitiesAndRoomResponse = ( - capabilities: (info: ResponseInfoType, data: Capabilities), + typealias CapabilitiesAndRoomResponse = ( + capabilities: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesResponse), room: (info: ResponseInfoType, data: Room) ) /// This is a convenience method which constructs a `/sequence` of the `capabilities` and `room` requests, refer to those /// methods for the documented behaviour of each method - public static func preparedCapabilitiesAndRoom( + static func preparedCapabilitiesAndRoom( roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - return try OpenGroupAPI + return try Network.SOGS .preparedSequence( requests: [ // Get the latest capabilities for the server (in case it's a new server or the @@ -290,9 +296,9 @@ public enum OpenGroupAPI { authMethod: authMethod, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) .tryMap { (info: ResponseInfoType, response: Network.BatchResponseMap) -> CapabilitiesAndRoomResponse in - let maybeCapabilities: Network.BatchSubResponse? = (response[.capabilities] as? Network.BatchSubResponse) + let maybeCapabilities: Network.BatchSubResponse? = (response[.capabilities] as? Network.BatchSubResponse) let maybeRoomResponse: Any? = response.data .first(where: { key, _ in switch key { @@ -305,7 +311,7 @@ public enum OpenGroupAPI { guard let capabilitiesInfo: ResponseInfoType = maybeCapabilities, - let capabilities: Capabilities = maybeCapabilities?.body, + let capabilities: Network.SOGS.CapabilitiesResponse = maybeCapabilities?.body, let roomInfo: ResponseInfoType = maybeRoom, let room: Room = maybeRoom?.body else { throw NetworkError.parsingFailed } @@ -317,31 +323,38 @@ public enum OpenGroupAPI { } } - public typealias CapabilitiesAndRoomsResponse = ( - capabilities: (info: ResponseInfoType, data: Capabilities), + typealias CapabilitiesAndRoomsResponse = ( + capabilities: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesResponse), rooms: (info: ResponseInfoType, data: [Room]) ) /// This is a convenience method which constructs a `/sequence` of the `capabilities` and `rooms` requests, refer to those /// methods for the documented behaviour of each method - public static func preparedCapabilitiesAndRooms( + static func preparedCapabilitiesAndRooms( authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - return try OpenGroupAPI + let preparedRequest = try Network.SOGS .preparedSequence( requests: [ // Get the latest capabilities for the server (in case it's a new server or the // cached ones are stale) - preparedCapabilities(authMethod: authMethod, using: dependencies), - preparedRooms(authMethod: authMethod, using: dependencies) + preparedCapabilities(authMethod: authMethod, skipAuthentication: skipAuthentication, using: dependencies), + preparedRooms(authMethod: authMethod, skipAuthentication: skipAuthentication, using: dependencies) ], authMethod: authMethod, + skipAuthentication: skipAuthentication, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + let finalRequest = (skipAuthentication ? + preparedRequest : + try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) + ) + + return finalRequest .tryMap { (info: ResponseInfoType, response: Network.BatchResponseMap) -> CapabilitiesAndRoomsResponse in - let maybeCapabilities: Network.BatchSubResponse? = (response[.capabilities] as? Network.BatchSubResponse) + let maybeCapabilities: Network.BatchSubResponse? = (response[.capabilities] as? Network.BatchSubResponse) let maybeRooms: Network.BatchSubResponse<[Room]>? = response.data .first(where: { key, _ in switch key { @@ -353,7 +366,7 @@ public enum OpenGroupAPI { guard let capabilitiesInfo: ResponseInfoType = maybeCapabilities, - let capabilities: Capabilities = maybeCapabilities?.body, + let capabilities: Network.SOGS.CapabilitiesResponse = maybeCapabilities?.body, let roomsInfo: ResponseInfoType = maybeRooms, let roomsResponse: Network.BatchSubResponse<[Room]> = maybeRooms, !roomsResponse.failedToParseBody @@ -370,7 +383,7 @@ public enum OpenGroupAPI { // MARK: - Messages /// Posts a new message to a room - public static func preparedSend( + static func preparedSend( plaintext: Data, roomToken: String, whisperTo: String?, @@ -390,7 +403,7 @@ public enum OpenGroupAPI { request: Request( method: .post, endpoint: Endpoint.roomMessage(roomToken), - body: SendMessageRequest( + body: SendSOGSMessageRequest( data: plaintext, signature: Data(signResult.signature), whisperTo: whisperTo, @@ -403,11 +416,11 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Returns a single message by ID - public static func preparedMessage( + static func preparedMessage( id: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -422,13 +435,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Edits a message, replacing its existing content with new content and a new signature /// /// **Note:** This edit may only be initiated by the creator of the post, and the poster must currently have write permissions in the room - public static func preparedMessageUpdate( + static func preparedMessageUpdate( id: Int64, plaintext: Data, fileIds: [Int64]?, @@ -458,11 +471,11 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Remove a message by its message id - public static func preparedMessageDelete( + static func preparedMessageDelete( id: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -478,7 +491,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Retrieves recent messages posted to this room @@ -486,7 +499,7 @@ public enum OpenGroupAPI { /// Returns the most recent limit messages (100 if no limit is given). This only returns extant messages, and always returns the latest /// versions: that is, deleted message indicators and pre-editing versions of messages are not returned. Messages are returned in order /// from most recent to least recent - public static func preparedRecentMessages( + static func preparedRecentMessages( roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -505,7 +518,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Retrieves messages from the room preceding a given id. @@ -514,7 +527,7 @@ public enum OpenGroupAPI { /// through batches of ever-older messages. As with .../recent, messages are returned in order from most recent to least recent. /// /// As with .../recent, this endpoint does not include deleted messages and always returns the current version, for edited messages. - public static func preparedMessagesBefore( + static func preparedMessagesBefore( messageId: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -534,7 +547,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Retrieves message updates from a room. This is the main message polling endpoint in SOGS. @@ -543,7 +556,7 @@ public enum OpenGroupAPI { /// sequence counter. Returns limit messages at a time (100 if no limit is given). Returned messages include any new messages, updates /// to existing messages (i.e. edits), and message deletions made to the room since the given update id. Messages are returned in "update" /// order, that is, in the order in which the change was applied to the room, from oldest the newest. - public static func preparedMessagesSince( + static func preparedMessagesSince( seqNo: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -563,7 +576,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Deletes all messages from a given sessionId within the provided rooms (or globally) on a server @@ -579,7 +592,7 @@ public enum OpenGroupAPI { /// - server: The server to delete messages from /// /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedMessagesDeleteAll( + static func preparedMessagesDeleteAll( sessionId: String, roomToken: String, authMethod: AuthenticationMethod, @@ -595,13 +608,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Reactions /// Returns the list of all reactors who have added a particular reaction to a particular message. - public static func preparedReactors( + static func preparedReactors( emoji: String, id: Int64, roomToken: String, @@ -611,7 +624,7 @@ public enum OpenGroupAPI { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji + throw SOGSError.invalidEmoji } return try Network.PreparedRequest( @@ -624,14 +637,14 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Adds a reaction to the given message in this room. The user must have read access in the room. /// /// Reactions are short strings of 1-12 unicode codepoints, typically emoji (or character sequences to produce an emoji variant, /// such as 👨🏿‍🦰, which is composed of 4 unicode "characters" but usually renders as a single emoji "Man: Dark Skin Tone, Red Hair"). - public static func preparedReactionAdd( + static func preparedReactionAdd( emoji: String, id: Int64, roomToken: String, @@ -641,7 +654,7 @@ public enum OpenGroupAPI { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji + throw SOGSError.invalidEmoji } return try Network.PreparedRequest( @@ -654,12 +667,12 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Removes a reaction from a post this room. The user must have read access in the room. This only removes the user's own reaction /// but does not affect the reactions of other users. - public static func preparedReactionDelete( + static func preparedReactionDelete( emoji: String, id: Int64, roomToken: String, @@ -669,7 +682,7 @@ public enum OpenGroupAPI { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji + throw SOGSError.invalidEmoji } return try Network.PreparedRequest( @@ -682,13 +695,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Removes all reactions of all users from a post in this room. The calling must have moderator permissions in the room. This endpoint /// can either remove a single reaction (e.g. remove all 🍆 reactions) by specifying it after the message id (following a /), or remove all /// reactions from the post by not including the / suffix of the URL. - public static func preparedReactionDeleteAll( + static func preparedReactionDeleteAll( emoji: String, id: Int64, roomToken: String, @@ -698,7 +711,7 @@ public enum OpenGroupAPI { /// URL(String:) won't convert raw emojis, so need to do a little encoding here. /// The raw emoji will come back when calling url.path guard let encodedEmoji: String = emoji.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - throw OpenGroupAPIError.invalidEmoji + throw SOGSError.invalidEmoji } return try Network.PreparedRequest( @@ -711,7 +724,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Pinning @@ -726,7 +739,7 @@ public enum OpenGroupAPI { /// Pinned messages that are already pinned will be re-pinned (that is, their pin timestamp and pinning admin user will be updated) - because pinned /// messages are returned in pinning-order this allows admins to order multiple pinned messages in a room by re-pinning (via this endpoint) in the /// order in which pinned messages should be displayed - public static func preparedPinMessage( + static func preparedPinMessage( id: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -742,13 +755,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Remove a message from this room's pinned message list /// /// The user must have `admin` (not just `moderator`) permissions in the room - public static func preparedUnpinMessage( + static func preparedUnpinMessage( id: Int64, roomToken: String, authMethod: AuthenticationMethod, @@ -764,13 +777,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Removes _all_ pinned messages from this room /// /// The user must have `admin` (not just `moderator`) permissions in the room - public static func preparedUnpinAll( + static func preparedUnpinAll( roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -785,12 +798,12 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Files - public static func preparedUpload( + static func preparedUpload( data: Data, roomToken: String, fileName: String? = nil, @@ -816,10 +829,10 @@ public enum OpenGroupAPI { requestTimeout: Network.fileUploadTimeout, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } - public static func downloadUrlString( + static func downloadUrlString( for fileId: String, server: String, roomToken: String @@ -827,24 +840,27 @@ public enum OpenGroupAPI { return "\(server)/\(Endpoint.roomFileIndividual(roomToken, fileId).path)" } - public static func preparedDownload( + static func preparedDownload( url: URL, roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - guard let fileId: String = Attachment.fileId(for: url.absoluteString) else { throw NetworkError.invalidURL } + guard let fileId: String = Network.FileServer.fileId(for: url.absoluteString) else { + throw NetworkError.invalidURL + } return try preparedDownload(fileId: fileId, roomToken: roomToken, authMethod: authMethod, using: dependencies) } - public static func preparedDownload( + static func preparedDownload( fileId: String, roomToken: String, authMethod: AuthenticationMethod, + skipAuthentication: Bool = false, using dependencies: Dependencies ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( + let preparedRequest = try Network.PreparedRequest( request: Request( endpoint: .roomFileIndividual(roomToken, fileId), authMethod: authMethod @@ -854,7 +870,11 @@ public enum OpenGroupAPI { requestTimeout: Network.fileDownloadTimeout, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + + return (skipAuthentication ? + preparedRequest : + try preparedRequest.signed(with: Network.SOGS.signRequest, using: dependencies) + ) } // MARK: - Inbox/Outbox (Message Requests) @@ -862,7 +882,7 @@ public enum OpenGroupAPI { /// Retrieves all of the user's current DMs (up to limit) /// /// **Note:** `inbox` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedInbox( + static func preparedInbox( authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest<[DirectMessage]?> { @@ -875,13 +895,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Polls for any DMs received since the given id, this method will return a `304` with an empty response if there are no messages /// /// **Note:** `inboxSince` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedInboxSince( + static func preparedInboxSince( id: Int64, authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -895,11 +915,11 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Remove all message requests from inbox, this methrod will return the number of messages deleted - public static func preparedClearInbox( + static func preparedClearInbox( requestTimeout: TimeInterval = Network.defaultTimeout, requestAndPathBuildTimeout: TimeInterval? = nil, authMethod: AuthenticationMethod, @@ -917,13 +937,13 @@ public enum OpenGroupAPI { requestAndPathBuildTimeout: requestAndPathBuildTimeout, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Delivers a direct message to a user via their blinded Session ID /// /// The body of this request is a JSON object containing a message key with a value of the encrypted-then-base64-encoded message to deliver - public static func preparedSend( + static func preparedSend( ciphertext: Data, toInboxFor blindedSessionId: String, authMethod: AuthenticationMethod, @@ -942,13 +962,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Retrieves all of the user's sent DMs (up to limit) /// /// **Note:** `outbox` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedOutbox( + static func preparedOutbox( authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest<[DirectMessage]?> { @@ -961,13 +981,13 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Polls for any DMs sent since the given id, this method will return a `304` with an empty response if there are no messages /// /// **Note:** `outboxSince` will return a `304` with an empty response if no messages (hence the optional return type) - public static func preparedOutboxSince( + static func preparedOutboxSince( id: Int64, authMethod: AuthenticationMethod, using dependencies: Dependencies @@ -981,7 +1001,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Users @@ -1017,7 +1037,7 @@ public enum OpenGroupAPI { /// - server: The server to delete messages from /// /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedUserBan( + static func preparedUserBan( sessionId: String, for timeout: TimeInterval? = nil, from roomTokens: [String]? = nil, @@ -1039,7 +1059,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Removes a user ban from specific rooms, or from the server globally @@ -1066,7 +1086,7 @@ public enum OpenGroupAPI { /// - server: The server to delete messages from /// /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedUserUnban( + static func preparedUserUnban( sessionId: String, from roomTokens: [String]?, authMethod: AuthenticationMethod, @@ -1086,7 +1106,7 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// Appoints or removes a moderator or admin @@ -1140,7 +1160,7 @@ public enum OpenGroupAPI { /// - server: The server to perform the permission changes on /// /// - dependencies: Injected dependencies (used for unit testing) - public static func preparedUserModeratorUpdate( + static func preparedUserModeratorUpdate( sessionId: String, moderator: Bool? = nil, admin: Bool? = nil, @@ -1170,18 +1190,18 @@ public enum OpenGroupAPI { additionalSignatureData: AdditionalSigningData(authMethod), using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } /// This is a convenience method which constructs a `/sequence` of the `userBan` and `userDeleteMessages` requests, refer to those /// methods for the documented behaviour of each method - public static func preparedUserBanAndDeleteAllMessages( + static func preparedUserBanAndDeleteAllMessages( sessionId: String, roomToken: String, authMethod: AuthenticationMethod, using dependencies: Dependencies ) throws -> Network.PreparedRequest> { - return try OpenGroupAPI + return try Network.SOGS .preparedSequence( requests: [ preparedUserBan( @@ -1200,7 +1220,7 @@ public enum OpenGroupAPI { authMethod: authMethod, using: dependencies ) - .signed(with: OpenGroupAPI.signRequest, using: dependencies) + .signed(with: Network.SOGS.signRequest, using: dependencies) } // MARK: - Authentication @@ -1222,7 +1242,7 @@ public enum OpenGroupAPI { !publicKey.isEmpty, let nonce: [UInt8] = dependencies[singleton: .crypto].generate(.randomBytes(16)), let timestampBytes: [UInt8] = "\(timestamp)".data(using: .ascii).map({ Array($0) }) - else { throw OpenGroupAPIError.signingFailed } + else { throw SOGSError.signingFailed } /// Get a hash of any body content let bodyHash: [UInt8]? = { @@ -1273,7 +1293,7 @@ public enum OpenGroupAPI { !dependencies[cache: .general].ed25519SecretKey.isEmpty, !dependencies[cache: .general].ed25519Seed.isEmpty, case .community(_, let publicKey, let hasCapabilities, let supportsBlinding, let forceBlinded) = authMethod.info - else { throw OpenGroupAPIError.signingFailed } + else { throw SOGSError.signingFailed } // If we have no capabilities or if the server supports blinded keys then sign using the blinded key if forceBlinded || !hasCapabilities || supportsBlinding { @@ -1291,7 +1311,7 @@ public enum OpenGroupAPI { ed25519SecretKey: dependencies[cache: .general].ed25519SecretKey ) ) - else { throw OpenGroupAPIError.signingFailed } + else { throw SOGSError.signingFailed } return ( publicKey: SessionId(.blinded15, publicKey: blinded15KeyPair.publicKey).hexString, @@ -1313,7 +1333,7 @@ public enum OpenGroupAPI { .ed25519KeyPair(seed: dependencies[cache: .general].ed25519Seed) ), case .standard(let signatureResult) = signature - else { throw OpenGroupAPIError.signingFailed } + else { throw SOGSError.signingFailed } return ( publicKey: SessionId(.unblinded, publicKey: ed25519KeyPair.publicKey).hexString, @@ -1335,7 +1355,7 @@ public enum OpenGroupAPI { let signatureResult: [UInt8] = dependencies[singleton: .crypto].generate( .signatureXed25519(data: messageBytes, curve25519PrivateKey: x25519SecretKey) ) - else { throw OpenGroupAPIError.signingFailed } + else { throw SOGSError.signingFailed } return ( publicKey: SessionId(.standard, publicKey: x25519PublicKey).hexString, @@ -1350,7 +1370,7 @@ public enum OpenGroupAPI { using dependencies: Dependencies ) throws -> Network.Destination { guard let signingData: AdditionalSigningData = preparedRequest.additionalSignatureData as? AdditionalSigningData else { - throw OpenGroupAPIError.signingFailed + throw SOGSError.signingFailed } return try preparedRequest.destination @@ -1358,7 +1378,7 @@ public enum OpenGroupAPI { } } -private extension OpenGroupAPI { +private extension Network.SOGS { struct AdditionalSigningData { let authMethod: AuthenticationMethod @@ -1369,7 +1389,7 @@ private extension OpenGroupAPI { } private extension Network.Destination { - func signed(data: OpenGroupAPI.AdditionalSigningData, body: Data?, using dependencies: Dependencies) throws -> Network.Destination { + func signed(data: Network.SOGS.AdditionalSigningData, body: Data?, using dependencies: Dependencies) throws -> Network.Destination { switch self { case .snode, .randomSnode, .randomSnodeLatestNetworkTimeTarget: throw NetworkError.unauthorised case .cached: return self @@ -1384,8 +1404,8 @@ private extension Network.Destination { } private extension Network.Destination.ServerInfo { - func signed(_ data: OpenGroupAPI.AdditionalSigningData, _ body: Data?, using dependencies: Dependencies) throws -> Network.Destination.ServerInfo { - return updated(with: try OpenGroupAPI.signatureHeaders( + func signed(_ data: Network.SOGS.AdditionalSigningData, _ body: Data?, using dependencies: Dependencies) throws -> Network.Destination.ServerInfo { + return updated(with: try Network.SOGS.signatureHeaders( url: url, method: method, body: body, diff --git a/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift b/SessionNetworkingKit/SOGS/SOGSEndpoint.swift similarity index 97% rename from SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift rename to SessionNetworkingKit/SOGS/SOGSEndpoint.swift index 9da8faf919..218bfadbe3 100644 --- a/SessionMessagingKit/Open Groups/Types/SOGSEndpoint.swift +++ b/SessionNetworkingKit/SOGS/SOGSEndpoint.swift @@ -3,10 +3,9 @@ // stringlint:disable import Foundation -import SessionSnodeKit -extension OpenGroupAPI { - public enum Endpoint: EndpointType { +public extension Network.SOGS { + enum Endpoint: EndpointType { // Utility case onion @@ -61,7 +60,7 @@ extension OpenGroupAPI { case userUnban(String) case userModerator(String) - public static var name: String { "OpenGroupAPI.Endpoint" } + public static var name: String { "SOGS.Endpoint" } public static var batchRequestVariant: Network.BatchRequest.Child.Variant = .sogs public static var excludedSubRequestHeaders: [HTTPHeader] = [ .sogsPubKey, .sogsTimestamp, .sogsNonce, .sogsSignature diff --git a/SessionMessagingKit/Open Groups/Types/OpenGroupAPIError.swift b/SessionNetworkingKit/SOGS/SOGSError.swift similarity index 92% rename from SessionMessagingKit/Open Groups/Types/OpenGroupAPIError.swift rename to SessionNetworkingKit/SOGS/SOGSError.swift index d5ab81cbe1..bf91640de0 100644 --- a/SessionMessagingKit/Open Groups/Types/OpenGroupAPIError.swift +++ b/SessionNetworkingKit/SOGS/SOGSError.swift @@ -4,7 +4,7 @@ import Foundation -public enum OpenGroupAPIError: Error, CustomStringConvertible { +public enum SOGSError: Error, CustomStringConvertible { case decryptionFailed case signingFailed case noPublicKey diff --git a/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift b/SessionNetworkingKit/SOGS/Types/HTTPHeader+SOGS.swift similarity index 94% rename from SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift rename to SessionNetworkingKit/SOGS/Types/HTTPHeader+SOGS.swift index 0b3dbc54ab..0c100cfbd1 100644 --- a/SessionMessagingKit/Open Groups/Types/HTTPHeader+OpenGroup.swift +++ b/SessionNetworkingKit/SOGS/Types/HTTPHeader+SOGS.swift @@ -3,7 +3,6 @@ // stringlint:disable import Foundation -import SessionSnodeKit public extension HTTPHeader { static let sogsPubKey: HTTPHeader = "X-SOGS-Pubkey" diff --git a/SessionMessagingKit/Open Groups/Types/HTTPQueryParam+OpenGroup.swift b/SessionNetworkingKit/SOGS/Types/HTTPQueryParam+SOGS.swift similarity index 96% rename from SessionMessagingKit/Open Groups/Types/HTTPQueryParam+OpenGroup.swift rename to SessionNetworkingKit/SOGS/Types/HTTPQueryParam+SOGS.swift index a9af9824ad..dd6c86e3c5 100644 --- a/SessionMessagingKit/Open Groups/Types/HTTPQueryParam+OpenGroup.swift +++ b/SessionNetworkingKit/SOGS/Types/HTTPQueryParam+SOGS.swift @@ -3,7 +3,6 @@ // stringlint:disable import Foundation -import SessionSnodeKit public extension HTTPQueryParam { static let publicKey: HTTPQueryParam = "public_key" diff --git a/SessionMessagingKit/Open Groups/Types/Personalization.swift b/SessionNetworkingKit/SOGS/Types/Personalization.swift similarity index 92% rename from SessionMessagingKit/Open Groups/Types/Personalization.swift rename to SessionNetworkingKit/SOGS/Types/Personalization.swift index b0b827b20d..5b24626fe9 100644 --- a/SessionMessagingKit/Open Groups/Types/Personalization.swift +++ b/SessionNetworkingKit/SOGS/Types/Personalization.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { public enum Personalization: String { case sharedKeys = "sogs.shared_keys" case authHeader = "sogs.auth_header" diff --git a/SessionMessagingKit/Open Groups/Types/Request+OpenGroupAPI.swift b/SessionNetworkingKit/SOGS/Types/Request+SOGS.swift similarity index 86% rename from SessionMessagingKit/Open Groups/Types/Request+OpenGroupAPI.swift rename to SessionNetworkingKit/SOGS/Types/Request+SOGS.swift index 6242a05447..5373174584 100644 --- a/SessionMessagingKit/Open Groups/Types/Request+OpenGroupAPI.swift +++ b/SessionNetworkingKit/SOGS/Types/Request+SOGS.swift @@ -1,13 +1,9 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation -import GRDB -import SessionSnodeKit import SessionUtilitiesKit -// MARK: Request - OpenGroupAPI - -public extension Request where Endpoint == OpenGroupAPI.Endpoint { +public extension Request where Endpoint == Network.SOGS.Endpoint { init( method: HTTPMethod = .get, endpoint: Endpoint, diff --git a/SessionMessagingKit/Open Groups/Types/UpdateTypes.swift b/SessionNetworkingKit/SOGS/Types/UpdateTypes.swift similarity index 85% rename from SessionMessagingKit/Open Groups/Types/UpdateTypes.swift rename to SessionNetworkingKit/SOGS/Types/UpdateTypes.swift index 93c63893fa..61baf5ee47 100644 --- a/SessionMessagingKit/Open Groups/Types/UpdateTypes.swift +++ b/SessionNetworkingKit/SOGS/Types/UpdateTypes.swift @@ -2,7 +2,7 @@ import Foundation -extension OpenGroupAPI { +extension Network.SOGS { enum UpdateTypes: String { case reaction = "r" } diff --git a/SessionNetworkingKit/SessionNetwork/Models/Info.swift b/SessionNetworkingKit/SessionNetwork/Models/Info.swift new file mode 100644 index 0000000000..6a9b8055f9 --- /dev/null +++ b/SessionNetworkingKit/SessionNetwork/Models/Info.swift @@ -0,0 +1,21 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension Network.SessionNetwork { + struct Info: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case timestamp = "t" + case statusCode = "status_code" + case price + case token + case network + } + + public let timestamp: Int64? // Request timestamp. (seconds) + public let statusCode: Int? // Status code of the request. + public let price: Price? + public let token: Token? + public let network: NetworkInfo? + } +} diff --git a/SessionNetworkingKit/SessionNetwork/Models/NetworkInfo.swift b/SessionNetworkingKit/SessionNetwork/Models/NetworkInfo.swift new file mode 100644 index 0000000000..28c49ff9d6 --- /dev/null +++ b/SessionNetworkingKit/SessionNetwork/Models/NetworkInfo.swift @@ -0,0 +1,17 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension Network.SessionNetwork { + struct NetworkInfo: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case networkSize = "network_size" // The number of nodes in the Session Network (integer) + case networkStakedTokens = "network_staked_tokens" // + case networkStakedUSD = "network_staked_usd" // + } + + public let networkSize: Int? + public let networkStakedTokens: Double? + public let networkStakedUSD: Double? + } +} diff --git a/SessionNetworkingKit/SessionNetwork/Models/Price.swift b/SessionNetworkingKit/SessionNetwork/Models/Price.swift new file mode 100644 index 0000000000..28eaa5d991 --- /dev/null +++ b/SessionNetworkingKit/SessionNetwork/Models/Price.swift @@ -0,0 +1,19 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension Network.SessionNetwork { + struct Price: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case tokenUsd = "usd" + case marketCapUsd = "usd_market_cap" + case priceTimestamp = "t_price" + case staleTimestamp = "t_stale" + } + + public let tokenUsd: Double? // Current token price (USD) + public let marketCapUsd: Double? // Current market cap value in (USD) + public let priceTimestamp: Int64? // The timestamp the price data is accurate at. (seconds) + public let staleTimestamp: Int64? // Stale timestamp for the price data. (seconds) + } +} diff --git a/SessionNetworkingKit/SessionNetwork/Models/Token.swift b/SessionNetworkingKit/SessionNetwork/Models/Token.swift new file mode 100644 index 0000000000..741da31784 --- /dev/null +++ b/SessionNetworkingKit/SessionNetwork/Models/Token.swift @@ -0,0 +1,17 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension Network.SessionNetwork { + struct Token: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case stakingRequirement = "staking_requirement" + case stakingRewardPool = "staking_reward_pool" + case contractAddress = "contract_address" + } + + public let stakingRequirement: Double? // The number of tokens required to stake a node. This is the effective "token amount" per node (SESH) + public let stakingRewardPool: Double? // The number of tokens in the staking reward pool (SESH) + public let contractAddress: String? // Token contract address (42 char Hexadecimal - Including 0x prefix) + } +} diff --git a/SessionNetworkingKit/SessionNetwork/SessionNetwork.swift b/SessionNetworkingKit/SessionNetwork/SessionNetwork.swift new file mode 100644 index 0000000000..c4c0e933c7 --- /dev/null +++ b/SessionNetworkingKit/SessionNetwork/SessionNetwork.swift @@ -0,0 +1,18 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation + +public extension Network { + enum SessionNetwork { + public static let workQueue: DispatchQueue = DispatchQueue( + label: "SessionNetworkAPI.workQueue", + qos: .userInitiated + ) + public static let client: HTTPClient = HTTPClient() + + static let networkAPIServer = "http://networkv1.getsession.org" + static let networkAPIServerPublicKey = "cbf461a4431dc9174dceef4421680d743a2a0e1a3131fc794240bcb0bc3dd449" + } +} diff --git a/SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI.swift b/SessionNetworkingKit/SessionNetwork/SessionNetworkAPI.swift similarity index 86% rename from SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI.swift rename to SessionNetworkingKit/SessionNetwork/SessionNetworkAPI.swift index b2be2d86ba..1d11aa7888 100644 --- a/SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI.swift +++ b/SessionNetworkingKit/SessionNetwork/SessionNetworkAPI.swift @@ -6,34 +6,31 @@ import Foundation import Combine import SessionUtilitiesKit -public enum SessionNetworkAPI { - public static let workQueue = DispatchQueue(label: "SessionNetworkAPI.workQueue", qos: .userInitiated) - public static let client = HTTPClient() - +public extension Network.SessionNetwork { // MARK: - Info /// General token info. This endpoint combines the `/price` and `/token` endpoint information. /// /// `GET/info` - public static func prepareInfo( + static func prepareInfo( using dependencies: Dependencies ) throws -> Network.PreparedRequest { return try Network.PreparedRequest( - request: Request( - endpoint: Network.NetworkAPI.Endpoint.info, + request: Request( + endpoint: Network.SessionNetwork.Endpoint.info, destination: .server( method: .get, - server: Network.NetworkAPI.networkAPIServer, + server: Network.SessionNetwork.networkAPIServer, queryParameters: [:], - x25519PublicKey: Network.NetworkAPI.networkAPIServerPublicKey + x25519PublicKey: Network.SessionNetwork.networkAPIServerPublicKey ) ), responseType: Info.self, requestAndPathBuildTimeout: Network.defaultTimeout, using: dependencies ) - .signed(with: SessionNetworkAPI.signRequest, using: dependencies) + .signed(with: Network.SessionNetwork.signRequest, using: dependencies) } // MARK: - Authentication diff --git a/SessionNetworkingKit/SessionNetwork/SessionNetworkEndpoint.swift b/SessionNetworkingKit/SessionNetwork/SessionNetworkEndpoint.swift new file mode 100644 index 0000000000..a96d9fd2d4 --- /dev/null +++ b/SessionNetworkingKit/SessionNetwork/SessionNetworkEndpoint.swift @@ -0,0 +1,21 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public extension Network.SessionNetwork { + public enum Endpoint: EndpointType { + case info + case price + case token + + public static var name: String { "NetworkAPI.Endpoint" } + + public var path: String { + switch self { + case .info: return "info" + case .price: return "price" + case .token: return "token" + } + } + } +} diff --git a/SessionNetworkingKit/SessionNetwork/Types/HTTPClient.swift b/SessionNetworkingKit/SessionNetwork/Types/HTTPClient.swift new file mode 100644 index 0000000000..eee5080e16 --- /dev/null +++ b/SessionNetworkingKit/SessionNetwork/Types/HTTPClient.swift @@ -0,0 +1,104 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import Combine +import GRDB +import SessionUtilitiesKit + +// MARK: - Log.Category + +public extension Log.Category { + static let sessionNetwork: Log.Category = .create("SessionNetwork", defaultLevel: .info) +} + +public extension Network.SessionNetwork { + final class HTTPClient { + private var cancellable: AnyCancellable? + private var dependencies: Dependencies? + + public func initialize(using dependencies: Dependencies) { + self.dependencies = dependencies + cancellable = getInfo(using: dependencies) + .subscribe(on: Network.SessionNetwork.workQueue, using: dependencies) + .receive(on: Network.SessionNetwork.workQueue) + .sink(receiveCompletion: { _ in }, receiveValue: { _ in }) + } + + public func getInfo(using dependencies: Dependencies) -> AnyPublisher { + cancellable?.cancel() + + let staleTimestampMs: Int64 = dependencies[singleton: .storage].read { db in db[.staleTimestampMs] }.defaulting(to: 0) + guard staleTimestampMs < dependencies[cache: .snodeAPI].currentOffsetTimestampMs() else { + return Just(()) + .delay(for: .milliseconds(500), scheduler: Network.SessionNetwork.workQueue) + .setFailureType(to: Error.self) + .flatMapStorageWritePublisher(using: dependencies) { [dependencies] db, info -> Bool in + db[.lastUpdatedTimestampMs] = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + return true + } + .eraseToAnyPublisher() + } + + return Result { + try Network.SessionNetwork + .prepareInfo(using: dependencies) + } + .publisher + .flatMap { [dependencies] in $0.send(using: dependencies) } + .map { _, info in info } + .flatMapStorageWritePublisher(using: dependencies) { [dependencies] db, info -> Bool in + // Token info + db[.lastUpdatedTimestampMs] = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + db[.tokenUsd] = info.price?.tokenUsd + db[.marketCapUsd] = info.price?.marketCapUsd + if let priceTimestamp = info.price?.priceTimestamp { + db[.priceTimestampMs] = priceTimestamp * 1000 + } else { + db[.priceTimestampMs] = nil + } + if let staleTimestamp = info.price?.staleTimestamp { + db[.staleTimestampMs] = staleTimestamp * 1000 + } else { + db[.staleTimestampMs] = nil + } + db[.stakingRequirement] = info.token?.stakingRequirement + db[.stakingRewardPool] = info.token?.stakingRewardPool + db[.contractAddress] = info.token?.contractAddress + // Network info + db[.networkSize] = info.network?.networkSize + db[.networkStakedTokens] = info.network?.networkStakedTokens + db[.networkStakedUSD] = info.network?.networkStakedUSD + + return true + } + .catch { error -> AnyPublisher in + Log.error(.sessionNetwork, "Failed to fetch token info due to error: \(error).") + return self.cleanUpSessionNetworkPageData(using: dependencies) + .map { _ in false } + .eraseToAnyPublisher() + + } + .eraseToAnyPublisher() + } + + private func cleanUpSessionNetworkPageData(using dependencies: Dependencies) -> AnyPublisher { + dependencies[singleton: .storage].writePublisher { db in + // Token info + db[.lastUpdatedTimestampMs] = nil + db[.tokenUsd] = nil + db[.marketCapUsd] = nil + db[.priceTimestampMs] = nil + db[.staleTimestampMs] = nil + db[.stakingRequirement] = nil + db[.stakingRewardPool] = nil + db[.contractAddress] = nil + // Network info + db[.networkSize] = nil + db[.networkStakedTokens] = nil + db[.networkStakedUSD] = nil + } + } + } +} diff --git a/SessionSnodeKit/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift b/SessionNetworkingKit/SessionNetwork/Types/HTTPHeader+SessionNetwork.swift similarity index 100% rename from SessionSnodeKit/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift rename to SessionNetworkingKit/SessionNetwork/Types/HTTPHeader+SessionNetwork.swift diff --git a/SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Database.swift b/SessionNetworkingKit/SessionNetwork/Types/KeyValueStore+SessionNetwork.swift similarity index 100% rename from SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Database.swift rename to SessionNetworkingKit/SessionNetwork/Types/KeyValueStore+SessionNetwork.swift diff --git a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift b/SessionNetworkingKit/StorageServer/Database/SnodeReceivedMessageInfo.swift similarity index 98% rename from SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift rename to SessionNetworkingKit/StorageServer/Database/SnodeReceivedMessageInfo.swift index a54cbc083f..eb8857f776 100644 --- a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift +++ b/SessionNetworkingKit/StorageServer/Database/SnodeReceivedMessageInfo.swift @@ -55,7 +55,7 @@ public extension SnodeReceivedMessageInfo { init( snode: LibSession.Snode, swarmPublicKey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, hash: String, expirationDateMs: Int64? ) { @@ -75,7 +75,7 @@ public extension SnodeReceivedMessageInfo { static func fetchLastNotExpired( _ db: ObservingDatabase, for snode: LibSession.Snode, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, swarmPublicKey: String, using dependencies: Dependencies ) throws -> SnodeReceivedMessageInfo? { diff --git a/SessionSnodeKit/Models/DeleteAllBeforeRequest.swift b/SessionNetworkingKit/StorageServer/Models/DeleteAllBeforeRequest.swift similarity index 89% rename from SessionSnodeKit/Models/DeleteAllBeforeRequest.swift rename to SessionNetworkingKit/StorageServer/Models/DeleteAllBeforeRequest.swift index 6e6e1e64af..bd720328f5 100644 --- a/SessionSnodeKit/Models/DeleteAllBeforeRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/DeleteAllBeforeRequest.swift @@ -5,22 +5,22 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public final class DeleteAllBeforeRequest: SnodeAuthenticatedRequestBody, UpdatableTimestamp { +extension Network.SnodeAPI { + final class DeleteAllBeforeRequest: SnodeAuthenticatedRequestBody, UpdatableTimestamp { enum CodingKeys: String, CodingKey { case beforeMs = "before" case namespace } let beforeMs: UInt64 - let namespace: SnodeAPI.Namespace? + let namespace: Network.SnodeAPI.Namespace? override var verificationBytes: [UInt8] { /// Ed25519 signature of `("delete_before" || namespace || before)`, signed by /// `pubkey`. Must be base64 encoded (json) or bytes (OMQ). `namespace` is the stringified /// version of the given non-default namespace parameter (i.e. "-42" or "all"), or the empty /// string for the default namespace (whether explicitly given or not). - SnodeAPI.Endpoint.deleteAllBefore.path.bytes + Network.SnodeAPI.Endpoint.deleteAllBefore.path.bytes .appending( contentsOf: (namespace == nil ? "all" : @@ -34,7 +34,7 @@ extension SnodeAPI { public init( beforeMs: UInt64, - namespace: SnodeAPI.Namespace?, + namespace: Network.SnodeAPI.Namespace?, authMethod: AuthenticationMethod, timestampMs: UInt64 ) { diff --git a/SessionSnodeKit/Models/DeleteAllBeforeResponse.swift b/SessionNetworkingKit/StorageServer/Models/DeleteAllBeforeResponse.swift similarity index 100% rename from SessionSnodeKit/Models/DeleteAllBeforeResponse.swift rename to SessionNetworkingKit/StorageServer/Models/DeleteAllBeforeResponse.swift diff --git a/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift b/SessionNetworkingKit/StorageServer/Models/DeleteAllMessagesRequest.swift similarity index 90% rename from SessionSnodeKit/Models/DeleteAllMessagesRequest.swift rename to SessionNetworkingKit/StorageServer/Models/DeleteAllMessagesRequest.swift index 57f0c28f2b..3dc0d5bdbe 100644 --- a/SessionSnodeKit/Models/DeleteAllMessagesRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/DeleteAllMessagesRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public final class DeleteAllMessagesRequest: SnodeAuthenticatedRequestBody, UpdatableTimestamp { +extension Network.SnodeAPI { + final class DeleteAllMessagesRequest: SnodeAuthenticatedRequestBody, UpdatableTimestamp { enum CodingKeys: String, CodingKey { case namespace } @@ -14,7 +14,7 @@ extension SnodeAPI { /// /// **Note:** If omitted when sending the request, messages are deleted from the default namespace /// only (namespace 0) - let namespace: SnodeAPI.Namespace + let namespace: Network.SnodeAPI.Namespace override var verificationBytes: [UInt8] { /// Ed25519 signature of `( "delete_all" || namespace || timestamp )`, where @@ -22,7 +22,7 @@ extension SnodeAPI { /// not), and otherwise the stringified version of the namespace parameter (i.e. "99" or "-42" or "all"). /// The signature must be signed by the ed25519 pubkey in `pubkey` (omitting the leading prefix). /// Must be base64 encoded for json requests; binary for OMQ requests. - SnodeAPI.Endpoint.deleteAll.path.bytes + Network.SnodeAPI.Endpoint.deleteAll.path.bytes .appending(contentsOf: namespace.verificationString.bytes) .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) } @@ -30,7 +30,7 @@ extension SnodeAPI { // MARK: - Init public init( - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, authMethod: AuthenticationMethod, timestampMs: UInt64 ) { diff --git a/SessionSnodeKit/Models/DeleteAllMessagesResponse.swift b/SessionNetworkingKit/StorageServer/Models/DeleteAllMessagesResponse.swift similarity index 100% rename from SessionSnodeKit/Models/DeleteAllMessagesResponse.swift rename to SessionNetworkingKit/StorageServer/Models/DeleteAllMessagesResponse.swift diff --git a/SessionSnodeKit/Models/DeleteMessagesRequest.swift b/SessionNetworkingKit/StorageServer/Models/DeleteMessagesRequest.swift similarity index 91% rename from SessionSnodeKit/Models/DeleteMessagesRequest.swift rename to SessionNetworkingKit/StorageServer/Models/DeleteMessagesRequest.swift index 4adc127240..c1736499ac 100644 --- a/SessionSnodeKit/Models/DeleteMessagesRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/DeleteMessagesRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class DeleteMessagesRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class DeleteMessagesRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case messageHashes = "messages" case requireSuccessfulDeletion = "required" @@ -17,7 +17,7 @@ extension SnodeAPI { /// Ed25519 signature of `("delete" || messages...)`; this signs the value constructed /// by concatenating "delete" and all `messages` values, using `pubkey` to sign. Must be base64 /// encoded for json requests; binary for OMQ requests. - SnodeAPI.Endpoint.deleteMessages.path.bytes + Network.SnodeAPI.Endpoint.deleteMessages.path.bytes .appending(contentsOf: messageHashes.joined().bytes) } diff --git a/SessionSnodeKit/Models/DeleteMessagesResponse.swift b/SessionNetworkingKit/StorageServer/Models/DeleteMessagesResponse.swift similarity index 100% rename from SessionSnodeKit/Models/DeleteMessagesResponse.swift rename to SessionNetworkingKit/StorageServer/Models/DeleteMessagesResponse.swift diff --git a/SessionSnodeKit/Models/GetExpiriesRequest.swift b/SessionNetworkingKit/StorageServer/Models/GetExpiriesRequest.swift similarity index 91% rename from SessionSnodeKit/Models/GetExpiriesRequest.swift rename to SessionNetworkingKit/StorageServer/Models/GetExpiriesRequest.swift index 091729fb3b..d1f85ebf2b 100644 --- a/SessionSnodeKit/Models/GetExpiriesRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/GetExpiriesRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class GetExpiriesRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class GetExpiriesRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case messageHashes = "messages" } @@ -16,7 +16,7 @@ extension SnodeAPI { override var verificationBytes: [UInt8] { /// Ed25519 signature of `("get_expiries" || timestamp || messages[0] || ... || messages[N])` /// where `timestamp` is expressed as a string (base10). The signature must be base64 encoded (json) or bytes (bt). - SnodeAPI.Endpoint.getExpiries.path.bytes + Network.SnodeAPI.Endpoint.getExpiries.path.bytes .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) .appending(contentsOf: messageHashes.joined().bytes) } diff --git a/SessionSnodeKit/Models/GetExpiriesResponse.swift b/SessionNetworkingKit/StorageServer/Models/GetExpiriesResponse.swift similarity index 100% rename from SessionSnodeKit/Models/GetExpiriesResponse.swift rename to SessionNetworkingKit/StorageServer/Models/GetExpiriesResponse.swift diff --git a/SessionSnodeKit/Models/GetMessagesRequest.swift b/SessionNetworkingKit/StorageServer/Models/GetMessagesRequest.swift similarity index 89% rename from SessionSnodeKit/Models/GetMessagesRequest.swift rename to SessionNetworkingKit/StorageServer/Models/GetMessagesRequest.swift index 3b7e7ca05b..c0c3f0ef7b 100644 --- a/SessionSnodeKit/Models/GetMessagesRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/GetMessagesRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class GetMessagesRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class GetMessagesRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case lastHash = "last_hash" case namespace @@ -13,7 +13,7 @@ extension SnodeAPI { } let lastHash: String - let namespace: SnodeAPI.Namespace? + let namespace: Network.SnodeAPI.Namespace? let maxCount: Int64? let maxSize: Int64? @@ -22,7 +22,7 @@ extension SnodeAPI { /// namespace), or `("retrieve" || timestamp)` when fetching from the default namespace. Both /// namespace and timestamp are the base10 expressions of the relevant values. Must be base64 /// encoded for json requests; binary for OMQ requests. - SnodeAPI.Endpoint.getMessages.path.bytes + Network.SnodeAPI.Endpoint.getMessages.path.bytes .appending(contentsOf: namespace?.verificationString.bytes) .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) } @@ -31,7 +31,7 @@ extension SnodeAPI { public init( lastHash: String, - namespace: SnodeAPI.Namespace?, + namespace: Network.SnodeAPI.Namespace?, authMethod: AuthenticationMethod, timestampMs: UInt64, maxCount: Int64? = nil, diff --git a/SessionSnodeKit/Models/GetMessagesResponse.swift b/SessionNetworkingKit/StorageServer/Models/GetMessagesResponse.swift similarity index 100% rename from SessionSnodeKit/Models/GetMessagesResponse.swift rename to SessionNetworkingKit/StorageServer/Models/GetMessagesResponse.swift diff --git a/SessionSnodeKit/Models/GetNetworkTimestampResponse.swift b/SessionNetworkingKit/StorageServer/Models/GetNetworkTimestampResponse.swift similarity index 75% rename from SessionSnodeKit/Models/GetNetworkTimestampResponse.swift rename to SessionNetworkingKit/StorageServer/Models/GetNetworkTimestampResponse.swift index 71428bab9d..d29488ffc2 100644 --- a/SessionSnodeKit/Models/GetNetworkTimestampResponse.swift +++ b/SessionNetworkingKit/StorageServer/Models/GetNetworkTimestampResponse.swift @@ -2,8 +2,8 @@ import Foundation -extension SnodeAPI { - public struct GetNetworkTimestampResponse: Decodable { +public extension Network.SnodeAPI { + struct GetNetworkTimestampResponse: Decodable { enum CodingKeys: String, CodingKey { case timestamp case version diff --git a/SessionSnodeKit/Models/LegacyGetMessagesRequest.swift b/SessionNetworkingKit/StorageServer/Models/LegacyGetMessagesRequest.swift similarity index 89% rename from SessionSnodeKit/Models/LegacyGetMessagesRequest.swift rename to SessionNetworkingKit/StorageServer/Models/LegacyGetMessagesRequest.swift index 70dc7aa3a8..ab008a94bb 100644 --- a/SessionSnodeKit/Models/LegacyGetMessagesRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/LegacyGetMessagesRequest.swift @@ -2,9 +2,9 @@ import Foundation -extension SnodeAPI { +extension Network.SnodeAPI { /// This is the legacy unauthenticated message retrieval request - public struct LegacyGetMessagesRequest: Encodable { + struct LegacyGetMessagesRequest: Encodable { enum CodingKeys: String, CodingKey { case pubkey case lastHash = "last_hash" @@ -15,7 +15,7 @@ extension SnodeAPI { let pubkey: String let lastHash: String - let namespace: SnodeAPI.Namespace? + let namespace: Network.SnodeAPI.Namespace? let maxCount: Int64? let maxSize: Int64? diff --git a/SessionSnodeKit/Models/LegacySendMessageRequest.swift b/SessionNetworkingKit/StorageServer/Models/LegacySendMessageRequest.swift similarity index 82% rename from SessionSnodeKit/Models/LegacySendMessageRequest.swift rename to SessionNetworkingKit/StorageServer/Models/LegacySendMessageRequest.swift index 08cfe72ef6..a9c1000119 100644 --- a/SessionSnodeKit/Models/LegacySendMessageRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/LegacySendMessageRequest.swift @@ -2,15 +2,15 @@ import Foundation -extension SnodeAPI { +extension Network.SnodeAPI { /// This is the legacy unauthenticated message store request - public struct LegacySendMessagesRequest: Encodable { + struct LegacySendMessagesRequest: Encodable { enum CodingKeys: String, CodingKey { case namespace } let message: SnodeMessage - let namespace: SnodeAPI.Namespace + let namespace: Network.SnodeAPI.Namespace // MARK: - Coding diff --git a/SessionSnodeKit/Models/ONSResolveRequest.swift b/SessionNetworkingKit/StorageServer/Models/ONSResolveRequest.swift similarity index 80% rename from SessionSnodeKit/Models/ONSResolveRequest.swift rename to SessionNetworkingKit/StorageServer/Models/ONSResolveRequest.swift index eaef290853..2e0534cf18 100644 --- a/SessionSnodeKit/Models/ONSResolveRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/ONSResolveRequest.swift @@ -2,8 +2,8 @@ import Foundation -extension SnodeAPI { - public struct ONSResolveRequest: Encodable { +extension Network.SnodeAPI { + struct ONSResolveRequest: Encodable { enum CodingKeys: String, CodingKey { case type case base64EncodedNameHash = "name_hash" diff --git a/SessionSnodeKit/Models/ONSResolveResponse.swift b/SessionNetworkingKit/StorageServer/Models/ONSResolveResponse.swift similarity index 93% rename from SessionSnodeKit/Models/ONSResolveResponse.swift rename to SessionNetworkingKit/StorageServer/Models/ONSResolveResponse.swift index 8ca850a123..527efed87a 100644 --- a/SessionSnodeKit/Models/ONSResolveResponse.swift +++ b/SessionNetworkingKit/StorageServer/Models/ONSResolveResponse.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class ONSResolveResponse: SnodeResponse { +public extension Network.SnodeAPI { + class ONSResolveResponse: SnodeResponse { internal struct Result: Codable { enum CodingKeys: String, CodingKey { case nonce diff --git a/SessionNetworkingKit/StorageServer/Models/OxenDaemonRPCRequest.swift b/SessionNetworkingKit/StorageServer/Models/OxenDaemonRPCRequest.swift new file mode 100644 index 0000000000..93ff3933a2 --- /dev/null +++ b/SessionNetworkingKit/StorageServer/Models/OxenDaemonRPCRequest.swift @@ -0,0 +1,23 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension Network.SnodeAPI { + struct OxenDaemonRPCRequest: Encodable { + private enum CodingKeys: String, CodingKey { + case endpoint + case body = "params" + } + + private let endpoint: String + private let body: T + + public init( + endpoint: Network.SnodeAPI.Endpoint, + body: T + ) { + self.endpoint = endpoint.path + self.body = body + } + } +} diff --git a/SessionSnodeKit/Models/RevokeSubaccountRequest.swift b/SessionNetworkingKit/StorageServer/Models/RevokeSubaccountRequest.swift similarity index 91% rename from SessionSnodeKit/Models/RevokeSubaccountRequest.swift rename to SessionNetworkingKit/StorageServer/Models/RevokeSubaccountRequest.swift index a7f1690e1a..7495a14258 100644 --- a/SessionSnodeKit/Models/RevokeSubaccountRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/RevokeSubaccountRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class RevokeSubaccountRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class RevokeSubaccountRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case subaccountsToRevoke = "revoke" } @@ -14,7 +14,7 @@ extension SnodeAPI { override var verificationBytes: [UInt8] { /// Ed25519 signature of `("revoke_subaccount" || timestamp || SUBACCOUNT_TAG_BYTES...)`; this /// signs the subaccount token, using `pubkey` to sign. Must be base64 encoded for json requests; binary for OMQ requests. - SnodeAPI.Endpoint.revokeSubaccount.path.bytes + Network.SnodeAPI.Endpoint.revokeSubaccount.path.bytes .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) .appending(contentsOf: Array(subaccountsToRevoke.joined())) } diff --git a/SessionSnodeKit/Models/RevokeSubaccountResponse.swift b/SessionNetworkingKit/StorageServer/Models/RevokeSubaccountResponse.swift similarity index 100% rename from SessionSnodeKit/Models/RevokeSubaccountResponse.swift rename to SessionNetworkingKit/StorageServer/Models/RevokeSubaccountResponse.swift diff --git a/SessionSnodeKit/Models/SendMessageRequest.swift b/SessionNetworkingKit/StorageServer/Models/SendMessageRequest.swift similarity index 89% rename from SessionSnodeKit/Models/SendMessageRequest.swift rename to SessionNetworkingKit/StorageServer/Models/SendMessageRequest.swift index b97ae8d672..69063606ac 100644 --- a/SessionSnodeKit/Models/SendMessageRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/SendMessageRequest.swift @@ -3,14 +3,14 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class SendMessageRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class SendMessageRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case namespace } let message: SnodeMessage - let namespace: SnodeAPI.Namespace + let namespace: Network.SnodeAPI.Namespace override var verificationBytes: [UInt8] { /// Ed25519 signature of `("store" || namespace || timestamp)`, where namespace and @@ -18,7 +18,7 @@ extension SnodeAPI { /// base64 encoded for json requests; binary for OMQ requests. For non-05 type pubkeys (i.e. non /// session ids) the signature will be verified using `pubkey`. For 05 pubkeys, see the following /// option. - SnodeAPI.Endpoint.sendMessage.path.bytes + Network.SnodeAPI.Endpoint.sendMessage.path.bytes .appending(contentsOf: namespace.verificationString.bytes) .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) } @@ -27,7 +27,7 @@ extension SnodeAPI { public init( message: SnodeMessage, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, authMethod: AuthenticationMethod, timestampMs: UInt64 ) { diff --git a/SessionSnodeKit/Models/SendMessageResponse.swift b/SessionNetworkingKit/StorageServer/Models/SendMessageResponse.swift similarity index 100% rename from SessionSnodeKit/Models/SendMessageResponse.swift rename to SessionNetworkingKit/StorageServer/Models/SendMessageResponse.swift diff --git a/SessionSnodeKit/Models/SnodeAuthenticatedRequestBody.swift b/SessionNetworkingKit/StorageServer/Models/SnodeAuthenticatedRequestBody.swift similarity index 97% rename from SessionSnodeKit/Models/SnodeAuthenticatedRequestBody.swift rename to SessionNetworkingKit/StorageServer/Models/SnodeAuthenticatedRequestBody.swift index 4bcaa17c42..307e612059 100644 --- a/SessionSnodeKit/Models/SnodeAuthenticatedRequestBody.swift +++ b/SessionNetworkingKit/StorageServer/Models/SnodeAuthenticatedRequestBody.swift @@ -3,7 +3,7 @@ import Foundation import SessionUtilitiesKit -public class SnodeAuthenticatedRequestBody: Encodable { +class SnodeAuthenticatedRequestBody: Encodable { private enum CodingKeys: String, CodingKey { case pubkey case subaccount diff --git a/SessionSnodeKit/Models/SnodeBatchRequest.swift b/SessionNetworkingKit/StorageServer/Models/SnodeBatchRequest.swift similarity index 96% rename from SessionSnodeKit/Models/SnodeBatchRequest.swift rename to SessionNetworkingKit/StorageServer/Models/SnodeBatchRequest.swift index e6a05c0c19..083dec184b 100644 --- a/SessionSnodeKit/Models/SnodeBatchRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/SnodeBatchRequest.swift @@ -3,7 +3,7 @@ import Foundation import SessionUtilitiesKit -internal extension SnodeAPI { +extension Network.SnodeAPI { struct BatchRequest: Encodable { let requests: [Child] @@ -38,7 +38,7 @@ internal extension SnodeAPI { case params } - let endpoint: SnodeAPI.Endpoint + let endpoint: Network.SnodeAPI.Endpoint /// The `jsonBodyEncoder` is used to avoid having to make `BatchSubRequest` a generic type (haven't found /// a good way to keep `BatchSubRequest` encodable using protocols unfortunately so need this work around) diff --git a/SessionSnodeKit/Models/SnodeRecursiveResponse.swift b/SessionNetworkingKit/StorageServer/Models/SnodeRecursiveResponse.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeRecursiveResponse.swift rename to SessionNetworkingKit/StorageServer/Models/SnodeRecursiveResponse.swift diff --git a/SessionSnodeKit/Models/SnodeRequest.swift b/SessionNetworkingKit/StorageServer/Models/SnodeRequest.swift similarity index 92% rename from SessionSnodeKit/Models/SnodeRequest.swift rename to SessionNetworkingKit/StorageServer/Models/SnodeRequest.swift index ab23b427b3..c88d159c8f 100644 --- a/SessionSnodeKit/Models/SnodeRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/SnodeRequest.swift @@ -9,13 +9,13 @@ public struct SnodeRequest: Encodable { case body = "params" } - internal let endpoint: SnodeAPI.Endpoint + internal let endpoint: Network.SnodeAPI.Endpoint internal let body: T // MARK: - Initialization public init( - endpoint: SnodeAPI.Endpoint, + endpoint: Network.SnodeAPI.Endpoint, body: T ) { self.endpoint = endpoint diff --git a/SessionSnodeKit/Models/SnodeResponse.swift b/SessionNetworkingKit/StorageServer/Models/SnodeResponse.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeResponse.swift rename to SessionNetworkingKit/StorageServer/Models/SnodeResponse.swift diff --git a/SessionSnodeKit/Models/SnodeSwarmItem.swift b/SessionNetworkingKit/StorageServer/Models/SnodeSwarmItem.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeSwarmItem.swift rename to SessionNetworkingKit/StorageServer/Models/SnodeSwarmItem.swift diff --git a/SessionSnodeKit/Models/UnrevokeSubaccountRequest.swift b/SessionNetworkingKit/StorageServer/Models/UnrevokeSubaccountRequest.swift similarity index 91% rename from SessionSnodeKit/Models/UnrevokeSubaccountRequest.swift rename to SessionNetworkingKit/StorageServer/Models/UnrevokeSubaccountRequest.swift index 63fb0f6b40..3f8cb6f30d 100644 --- a/SessionSnodeKit/Models/UnrevokeSubaccountRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/UnrevokeSubaccountRequest.swift @@ -3,8 +3,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class UnrevokeSubaccountRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class UnrevokeSubaccountRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case subaccountsToUnrevoke = "unrevoke" } @@ -14,7 +14,7 @@ extension SnodeAPI { override var verificationBytes: [UInt8] { /// Ed25519 signature of `("unrevoke_subaccount" || timestamp || subaccount)`; this signs /// the subaccount token, using `pubkey` to sign. Must be base64 encoded for json requests; binary for OMQ requests. - SnodeAPI.Endpoint.unrevokeSubaccount.path.bytes + Network.SnodeAPI.Endpoint.unrevokeSubaccount.path.bytes .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes) .appending(contentsOf: Array(subaccountsToUnrevoke.joined())) } diff --git a/SessionSnodeKit/Models/UnrevokeSubaccountResponse.swift b/SessionNetworkingKit/StorageServer/Models/UnrevokeSubaccountResponse.swift similarity index 100% rename from SessionSnodeKit/Models/UnrevokeSubaccountResponse.swift rename to SessionNetworkingKit/StorageServer/Models/UnrevokeSubaccountResponse.swift diff --git a/SessionSnodeKit/Models/UpdateExpiryAllRequest.swift b/SessionNetworkingKit/StorageServer/Models/UpdateExpiryAllRequest.swift similarity index 90% rename from SessionSnodeKit/Models/UpdateExpiryAllRequest.swift rename to SessionNetworkingKit/StorageServer/Models/UpdateExpiryAllRequest.swift index 184e767764..dbc4fbf3ff 100644 --- a/SessionSnodeKit/Models/UpdateExpiryAllRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/UpdateExpiryAllRequest.swift @@ -5,8 +5,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class UpdateExpiryAllRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class UpdateExpiryAllRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case expiryMs = "expiry" case namespace @@ -19,14 +19,14 @@ extension SnodeAPI { /// /// **Note:** If omitted when sending the request, message expiries are updated from the default namespace /// only (namespace 0) - let namespace: SnodeAPI.Namespace? + let namespace: Network.SnodeAPI.Namespace? override var verificationBytes: [UInt8] { /// Ed25519 signature of `("expire_all" || namespace || expiry)`, signed by `pubkey`. Must be /// base64 encoded (json) or bytes (OMQ). namespace should be the stringified namespace for /// non-default namespace expiries (i.e. "42", "-99", "all"), or an empty string for the default /// namespace (whether or not explicitly provided). - SnodeAPI.Endpoint.expireAll.path.bytes + Network.SnodeAPI.Endpoint.expireAll.path.bytes .appending( contentsOf: (namespace == nil ? "all" : @@ -40,7 +40,7 @@ extension SnodeAPI { public init( expiryMs: UInt64, - namespace: SnodeAPI.Namespace?, + namespace: Network.SnodeAPI.Namespace?, authMethod: AuthenticationMethod ) { self.expiryMs = expiryMs diff --git a/SessionSnodeKit/Models/UpdateExpiryAllResponse.swift b/SessionNetworkingKit/StorageServer/Models/UpdateExpiryAllResponse.swift similarity index 100% rename from SessionSnodeKit/Models/UpdateExpiryAllResponse.swift rename to SessionNetworkingKit/StorageServer/Models/UpdateExpiryAllResponse.swift diff --git a/SessionSnodeKit/Models/UpdateExpiryRequest.swift b/SessionNetworkingKit/StorageServer/Models/UpdateExpiryRequest.swift similarity index 95% rename from SessionSnodeKit/Models/UpdateExpiryRequest.swift rename to SessionNetworkingKit/StorageServer/Models/UpdateExpiryRequest.swift index ba3546abac..6e237557d0 100644 --- a/SessionSnodeKit/Models/UpdateExpiryRequest.swift +++ b/SessionNetworkingKit/StorageServer/Models/UpdateExpiryRequest.swift @@ -5,8 +5,8 @@ import Foundation import SessionUtilitiesKit -extension SnodeAPI { - public class UpdateExpiryRequest: SnodeAuthenticatedRequestBody { +extension Network.SnodeAPI { + class UpdateExpiryRequest: SnodeAuthenticatedRequestBody { enum CodingKeys: String, CodingKey { case messageHashes = "messages" case expiryMs = "expiry" @@ -39,7 +39,7 @@ extension SnodeAPI { /// ` || messages[N])` where `expiry` is the expiry timestamp expressed as a string. /// `ShortenOrExtend` is string signature must be base64 "shorten" if the shorten option is given (and true), /// "extend" if `extend` is true, and empty otherwise. The signature must be base64 encoded (json) or bytes (bt). - SnodeAPI.Endpoint.expire.path.bytes + Network.SnodeAPI.Endpoint.expire.path.bytes .appending(contentsOf: (shorten == true ? "shorten".bytes : [])) .appending(contentsOf: (extend == true ? "extend".bytes : [])) .appending(contentsOf: "\(expiryMs)".data(using: .ascii)?.bytes) diff --git a/SessionSnodeKit/Models/UpdateExpiryResponse.swift b/SessionNetworkingKit/StorageServer/Models/UpdateExpiryResponse.swift similarity index 100% rename from SessionSnodeKit/Models/UpdateExpiryResponse.swift rename to SessionNetworkingKit/StorageServer/Models/UpdateExpiryResponse.swift diff --git a/SessionNetworkingKit/StorageServer/SnodeAPI.swift b/SessionNetworkingKit/StorageServer/SnodeAPI.swift new file mode 100644 index 0000000000..68e7357e4a --- /dev/null +++ b/SessionNetworkingKit/StorageServer/SnodeAPI.swift @@ -0,0 +1,859 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import Combine +import GRDB +import Punycode +import SessionUtilitiesKit + +public extension Network { + enum SnodeAPI { + // MARK: - Settings + + public static let maxRetryCount: Int = 8 + + // MARK: - Batching & Polling + + public typealias PollResponse = [SnodeAPI.Namespace: (info: ResponseInfoType, data: PreparedGetMessagesResponse?)] + + public static func preparedPoll( + _ db: ObservingDatabase, + namespaces: [SnodeAPI.Namespace], + refreshingConfigHashes: [String] = [], + from snode: LibSession.Snode, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + // Determine the maxSize each namespace in the request should take up + var requests: [any ErasedPreparedRequest] = [] + let namespaceMaxSizeMap: [SnodeAPI.Namespace: Int64] = SnodeAPI.Namespace.maxSizeMap(for: namespaces) + let fallbackSize: Int64 = (namespaceMaxSizeMap.values.min() ?? 1) + + // If we have any config hashes to refresh TTLs then add those requests first + if !refreshingConfigHashes.isEmpty { + let updatedExpiryMS: Int64 = ( + dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + + (30 * 24 * 60 * 60 * 1000) // 30 days + ) + requests.append( + try SnodeAPI.preparedUpdateExpiry( + serverHashes: refreshingConfigHashes, + updatedExpiryMs: updatedExpiryMS, + extendOnly: true, + ignoreValidationFailure: true, + explicitTargetNode: snode, + authMethod: authMethod, + using: dependencies + ) + ) + } + + // Add the various 'getMessages' requests + requests.append( + contentsOf: try namespaces.map { namespace -> any ErasedPreparedRequest in + try SnodeAPI.preparedGetMessages( + db, + namespace: namespace, + snode: snode, + maxSize: namespaceMaxSizeMap[namespace] + .defaulting(to: fallbackSize), + authMethod: authMethod, + using: dependencies + ) + } + ) + + return try preparedBatch( + requests: requests, + requireAllBatchResponses: true, + snode: snode, + swarmPublicKey: try authMethod.swarmPublicKey, + using: dependencies + ) + .map { (_: ResponseInfoType, batchResponse: Network.BatchResponse) -> [SnodeAPI.Namespace: (info: ResponseInfoType, data: PreparedGetMessagesResponse?)] in + let messageResponses: [Network.BatchSubResponse] = batchResponse + .compactMap { $0 as? Network.BatchSubResponse } + + return zip(namespaces, messageResponses) + .reduce(into: [:]) { result, next in + guard let messageResponse: PreparedGetMessagesResponse = next.1.body else { return } + + result[next.0] = (next.1, messageResponse) + } + } + } + + public static func preparedBatch( + requests: [any ErasedPreparedRequest], + requireAllBatchResponses: Bool, + snode: LibSession.Snode? = nil, + swarmPublicKey: String, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try SnodeAPI + .prepareRequest( + request: { + switch snode { + case .none: + return try Request( + endpoint: .batch, + swarmPublicKey: swarmPublicKey, + body: Network.BatchRequest(requestsKey: .requests, requests: requests) + ) + + case .some(let snode): + return try Request( + endpoint: .batch, + snode: snode, + swarmPublicKey: swarmPublicKey, + body: Network.BatchRequest(requestsKey: .requests, requests: requests) + ) + } + }(), + responseType: Network.BatchResponse.self, + requireAllBatchResponses: requireAllBatchResponses, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) + } + + public static func preparedSequence( + requests: [any ErasedPreparedRequest], + requireAllBatchResponses: Bool, + swarmPublicKey: String, + snodeRetrievalRetryCount: Int, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .sequence, + swarmPublicKey: swarmPublicKey, + body: Network.BatchRequest(requestsKey: .requests, requests: requests), + snodeRetrievalRetryCount: snodeRetrievalRetryCount + ), + responseType: Network.BatchResponse.self, + requireAllBatchResponses: requireAllBatchResponses, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) + } + + // MARK: - Retrieve + + public typealias PreparedGetMessagesResponse = (messages: [SnodeReceivedMessage], lastHash: String?) + + public static func preparedGetMessages( + _ db: ObservingDatabase, + namespace: SnodeAPI.Namespace, + snode: LibSession.Snode, + maxSize: Int64? = nil, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + let maybeLastHash: String? = try SnodeReceivedMessageInfo + .fetchLastNotExpired( + db, + for: snode, + namespace: namespace, + swarmPublicKey: try authMethod.swarmPublicKey, + using: dependencies + )? + .hash + let preparedRequest: Network.PreparedRequest = try { + // Check if this namespace requires authentication + guard namespace.requiresReadAuthentication else { + return try SnodeAPI.prepareRequest( + request: Request( + endpoint: .getMessages, + swarmPublicKey: try authMethod.swarmPublicKey, + body: LegacyGetMessagesRequest( + pubkey: try authMethod.swarmPublicKey, + lastHash: (maybeLastHash ?? ""), + namespace: namespace, + maxCount: nil, + maxSize: maxSize + ) + ), + responseType: GetMessagesResponse.self, + using: dependencies + ) + } + + return try SnodeAPI.prepareRequest( + request: Request( + endpoint: .getMessages, + swarmPublicKey: try authMethod.swarmPublicKey, + body: GetMessagesRequest( + lastHash: (maybeLastHash ?? ""), + namespace: namespace, + authMethod: authMethod, + timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs(), + maxSize: maxSize + ) + ), + responseType: GetMessagesResponse.self, + using: dependencies + ) + }() + + return preparedRequest + .tryMap { _, response -> (messages: [SnodeReceivedMessage], lastHash: String?) in + return ( + try response.messages.compactMap { rawMessage -> SnodeReceivedMessage? in + SnodeReceivedMessage( + snode: snode, + publicKey: try authMethod.swarmPublicKey, + namespace: namespace, + rawMessage: rawMessage + ) + }, + maybeLastHash + ) + } + } + + public static func getSessionID( + for onsName: String, + using dependencies: Dependencies + ) -> AnyPublisher { + let validationCount = 3 + + // The name must be lowercased + let onsName = onsName.lowercased().idnaEncoded ?? onsName.lowercased() + + // Hash the ONS name using BLAKE2b + guard + let nameHash = dependencies[singleton: .crypto].generate( + .hash(message: Array(onsName.utf8)) + ) + else { + return Fail(error: SnodeAPIError.onsHashingFailed) + .eraseToAnyPublisher() + } + + // Ask 3 different snodes for the Session ID associated with the given name hash + let base64EncodedNameHash = nameHash.toBase64() + + return dependencies[singleton: .network] + .getRandomNodes(count: validationCount) + .tryFlatMap { nodes in + Publishers.MergeMany( + try nodes.map { snode in + try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .oxenDaemonRPCCall, + snode: snode, + body: OxenDaemonRPCRequest( + endpoint: .daemonOnsResolve, + body: ONSResolveRequest( + type: 0, // type 0 means Session + base64EncodedNameHash: base64EncodedNameHash + ) + ) + ), + responseType: ONSResolveResponse.self, + using: dependencies + ) + .tryMap { _, response -> String in + try dependencies[singleton: .crypto].tryGenerate( + .sessionId(name: onsName, response: response) + ) + } + .send(using: dependencies) + .map { _, sessionId in sessionId } + .eraseToAnyPublisher() + } + ) + } + .collect() + .tryMap { results -> String in + guard results.count == validationCount, Set(results).count == 1 else { + throw SnodeAPIError.onsValidationFailed + } + + return results[0] + } + .eraseToAnyPublisher() + } + + public static func preparedGetExpiries( + of serverHashes: [String], + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .getExpiries, + swarmPublicKey: try authMethod.swarmPublicKey, + body: GetExpiriesRequest( + messageHashes: serverHashes, + authMethod: authMethod, + timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + ) + ), + responseType: GetExpiriesResponse.self, + using: dependencies + ) + } + + // MARK: - Store + + public static func preparedSendMessage( + message: SnodeMessage, + in namespace: Namespace, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + let request: Network.PreparedRequest = try { + // Check if this namespace requires authentication + guard namespace.requiresWriteAuthentication else { + return try SnodeAPI.prepareRequest( + request: Request( + endpoint: .sendMessage, + swarmPublicKey: try authMethod.swarmPublicKey, + body: LegacySendMessagesRequest( + message: message, + namespace: namespace + ), + snodeRetrievalRetryCount: 0 // The SendMessageJob already has a retry mechanism + ), + responseType: SendMessagesResponse.self, + requestAndPathBuildTimeout: Network.defaultTimeout, + using: dependencies + ) + } + + return try SnodeAPI.prepareRequest( + request: Request( + endpoint: .sendMessage, + swarmPublicKey: try authMethod.swarmPublicKey, + body: SendMessageRequest( + message: message, + namespace: namespace, + authMethod: authMethod, + timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + ), + snodeRetrievalRetryCount: 0 // The SendMessageJob already has a retry mechanism + ), + responseType: SendMessagesResponse.self, + requestAndPathBuildTimeout: Network.defaultTimeout, + using: dependencies + ) + }() + + return request + .tryMap { _, response -> SendMessagesResponse in + try response.validateResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + using: dependencies + ) + + return response + } + } + + // MARK: - Edit + + public static func preparedUpdateExpiry( + serverHashes: [String], + updatedExpiryMs: Int64, + shortenOnly: Bool? = nil, + extendOnly: Bool? = nil, + ignoreValidationFailure: Bool = false, + explicitTargetNode: LibSession.Snode? = nil, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest<[String: UpdateExpiryResponseResult]> { + // ShortenOnly and extendOnly cannot be true at the same time + guard shortenOnly == nil || extendOnly == nil else { throw NetworkError.invalidPreparedRequest } + + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .expire, + swarmPublicKey: try authMethod.swarmPublicKey, + body: UpdateExpiryRequest( + messageHashes: serverHashes, + expiryMs: UInt64(updatedExpiryMs), + shorten: shortenOnly, + extend: extendOnly, + authMethod: authMethod + ) + ), + responseType: UpdateExpiryResponse.self, + using: dependencies + ) + .tryMap { _, response -> [String: UpdateExpiryResponseResult] in + do { + return try response.validResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: serverHashes, + using: dependencies + ) + } + catch { + guard ignoreValidationFailure else { throw error } + + return [:] + } + } + .handleEvents( + receiveOutput: { _, result in + /// Since we have updated the TTL we need to make sure we also update the local + /// `SnodeReceivedMessageInfo.expirationDateMs` values so they match the updated swarm, if + /// we had a specific `snode` we we're sending the request to then we should use those values, otherwise + /// we can just grab the first value from the response and use that + let maybeTargetResult: UpdateExpiryResponseResult? = { + guard let snode: LibSession.Snode = explicitTargetNode else { + return result.first?.value + } + + return result[snode.ed25519PubkeyHex] + }() + guard + let targetResult: UpdateExpiryResponseResult = maybeTargetResult, + let groupedExpiryResult: [UInt64: [String]] = targetResult.changed + .updated(with: targetResult.unchanged) + .groupedByValue() + .nullIfEmpty + else { return } + + dependencies[singleton: .storage].writeAsync { db in + try groupedExpiryResult.forEach { updatedExpiry, hashes in + try SnodeReceivedMessageInfo + .filter(hashes.contains(SnodeReceivedMessageInfo.Columns.hash)) + .updateAll( + db, + SnodeReceivedMessageInfo.Columns.expirationDateMs + .set(to: updatedExpiry) + ) + } + } + } + ) + } + + public static func preparedRevokeSubaccounts( + subaccountsToRevoke: [[UInt8]], + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + let timestampMs: UInt64 = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .revokeSubaccount, + swarmPublicKey: try authMethod.swarmPublicKey, + body: RevokeSubaccountRequest( + subaccountsToRevoke: subaccountsToRevoke, + authMethod: authMethod, + timestampMs: timestampMs + ) + ), + responseType: RevokeSubaccountResponse.self, + using: dependencies + ) + .tryMap { _, response -> Void in + try response.validateResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: (subaccountsToRevoke, timestampMs), + using: dependencies + ) + + return () + } + } + + public static func preparedUnrevokeSubaccounts( + subaccountsToUnrevoke: [[UInt8]], + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + let timestampMs: UInt64 = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .unrevokeSubaccount, + swarmPublicKey: try authMethod.swarmPublicKey, + body: UnrevokeSubaccountRequest( + subaccountsToUnrevoke: subaccountsToUnrevoke, + authMethod: authMethod, + timestampMs: timestampMs + ) + ), + responseType: UnrevokeSubaccountResponse.self, + using: dependencies + ) + .tryMap { _, response -> Void in + try response.validateResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: (subaccountsToUnrevoke, timestampMs), + using: dependencies + ) + + return () + } + } + + // MARK: - Delete + + public static func preparedDeleteMessages( + serverHashes: [String], + requireSuccessfulDeletion: Bool, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest<[String: Bool]> { + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .deleteMessages, + swarmPublicKey: try authMethod.swarmPublicKey, + body: DeleteMessagesRequest( + messageHashes: serverHashes, + requireSuccessfulDeletion: requireSuccessfulDeletion, + authMethod: authMethod + ) + ), + responseType: DeleteMessagesResponse.self, + using: dependencies + ) + .tryMap { _, response -> [String: Bool] in + let validResultMap: [String: Bool] = try response.validResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: serverHashes, + using: dependencies + ) + + // If `validResultMap` didn't throw then at least one service node + // deleted successfully so we should mark the hash as invalid so we + // don't try to fetch updates using that hash going forward (if we + // do we would end up re-fetching all old messages) + dependencies[singleton: .storage].writeAsync { db in + try? SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( + db, + potentiallyInvalidHashes: serverHashes + ) + } + + return validResultMap + } + } + + + /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. + public static func preparedDeleteAllMessages( + namespace: SnodeAPI.Namespace, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest<[String: Bool]> { + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .deleteAll, + swarmPublicKey: try authMethod.swarmPublicKey, + requiresLatestNetworkTime: true, + body: DeleteAllMessagesRequest( + namespace: namespace, + authMethod: authMethod, + timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + ), + snodeRetrievalRetryCount: 0 + ), + responseType: DeleteAllMessagesResponse.self, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) + .tryMap { info, response -> [String: Bool] in + guard let targetInfo: LatestTimestampResponseInfo = info as? LatestTimestampResponseInfo else { + throw NetworkError.invalidResponse + } + + return try response.validResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: targetInfo.timestampMs, + using: dependencies + ) + } + } + + /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. + public static func preparedDeleteAllMessages( + beforeMs: UInt64, + namespace: SnodeAPI.Namespace, + authMethod: AuthenticationMethod, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest<[String: Bool]> { + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .deleteAllBefore, + swarmPublicKey: try authMethod.swarmPublicKey, + requiresLatestNetworkTime: true, + body: DeleteAllBeforeRequest( + beforeMs: beforeMs, + namespace: namespace, + authMethod: authMethod, + timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + ) + ), + responseType: DeleteAllMessagesResponse.self, + retryCount: maxRetryCount, + using: dependencies + ) + .tryMap { _, response -> [String: Bool] in + try response.validResultMap( + swarmPublicKey: try authMethod.swarmPublicKey, + validationData: beforeMs, + using: dependencies + ) + } + } + + // MARK: - Internal API + + public static func preparedGetNetworkTime( + from snode: LibSession.Snode, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try SnodeAPI + .prepareRequest( + request: Request, Endpoint>( + endpoint: .getInfo, + snode: snode, + body: [:] + ), + responseType: GetNetworkTimestampResponse.self, + using: dependencies + ) + .map { _, response in + // Assume we've fetched the networkTime in order to send a message to the specified snode, in + // which case we want to update the 'clockOffsetMs' value for subsequent requests + let offset = (Int64(response.timestamp) - Int64(floor(dependencies.dateNow.timeIntervalSince1970 * 1000))) + dependencies.mutate(cache: .snodeAPI) { $0.setClockOffsetMs(offset) } + + return response.timestamp + } + } + + // MARK: - Convenience + + private static func prepareRequest( + request: Request, + responseType: R.Type, + requireAllBatchResponses: Bool = true, + retryCount: Int = 0, + requestTimeout: TimeInterval = Network.defaultTimeout, + requestAndPathBuildTimeout: TimeInterval? = nil, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try Network.PreparedRequest( + request: request, + responseType: responseType, + requireAllBatchResponses: requireAllBatchResponses, + retryCount: retryCount, + requestTimeout: requestTimeout, + requestAndPathBuildTimeout: requestAndPathBuildTimeout, + using: dependencies + ) + .handleEvents( + receiveOutput: { _, response in + switch response { + case let snodeResponse as SnodeResponse: + // Update the network offset based on the response so subsequent requests have + // the correct network offset time + let offset = (Int64(snodeResponse.timeOffset) - Int64(floor(dependencies.dateNow.timeIntervalSince1970 * 1000))) + dependencies.mutate(cache: .snodeAPI) { + $0.setClockOffsetMs(offset) + + // Extract and store hard fork information if returned + guard snodeResponse.hardForkVersion.count > 1 else { return } + + if snodeResponse.hardForkVersion[1] > $0.softfork { + $0.softfork = snodeResponse.hardForkVersion[1] + dependencies[defaults: .standard, key: .softfork] = $0.softfork + } + + if snodeResponse.hardForkVersion[0] > $0.hardfork { + $0.hardfork = snodeResponse.hardForkVersion[0] + dependencies[defaults: .standard, key: .hardfork] = $0.hardfork + $0.softfork = snodeResponse.hardForkVersion[1] + dependencies[defaults: .standard, key: .softfork] = $0.softfork + } + } + + default: break + } + } + ) + } + } +} + +// MARK: - Publisher Convenience + +public extension Publisher where Output == Set { + func tryMapWithRandomSnode( + using dependencies: Dependencies, + _ transform: @escaping (LibSession.Snode) throws -> T + ) -> AnyPublisher { + return self + .tryMap { swarm -> T in + var remainingSnodes: Set = swarm + let snode: LibSession.Snode = try dependencies.popRandomElement(&remainingSnodes) ?? { + throw SnodeAPIError.insufficientSnodes + }() + + return try transform(snode) + } + .eraseToAnyPublisher() + } + + func tryFlatMapWithRandomSnode( + maxPublishers: Subscribers.Demand = .unlimited, + retry retries: Int = 0, + drainBehaviour: ThreadSafeObject = .alwaysRandom, + using dependencies: Dependencies, + _ transform: @escaping (LibSession.Snode) throws -> P + ) -> AnyPublisher where T == P.Output, P: Publisher, P.Failure == Error { + return self + .mapError { $0 } + .flatMap(maxPublishers: maxPublishers) { swarm -> AnyPublisher in + // If we don't want to reuse a specific snode multiple times then just grab a + // random one from the swarm every time + var remainingSnodes: Set = drainBehaviour.performUpdateAndMap { behaviour in + switch behaviour { + case .alwaysRandom: return (behaviour, swarm) + case .limitedReuse(_, let targetSnode, _, let usedSnodes, let swarmHash): + // If we've used all of the snodes or the swarm has changed then reset the used list + guard swarmHash == swarm.hashValue && (targetSnode != nil || usedSnodes != swarm) else { + return (behaviour.reset(), swarm) + } + + return (behaviour, swarm.subtracting(usedSnodes)) + } + } + var lastError: Error? + + return Just(()) + .setFailureType(to: Error.self) + .tryFlatMap(maxPublishers: maxPublishers) { _ -> AnyPublisher in + let snode: LibSession.Snode = try drainBehaviour.performUpdateAndMap { behaviour in + switch behaviour { + case .limitedReuse(_, .some(let targetSnode), _, _, _): + return (behaviour.use(snode: targetSnode, from: swarm), targetSnode) + default: break + } + + // Select the next snode + let result: LibSession.Snode = try dependencies.popRandomElement(&remainingSnodes) ?? { + throw SnodeAPIError.ranOutOfRandomSnodes(lastError) + }() + + return (behaviour.use(snode: result, from: swarm), result) + } + + return try transform(snode) + .eraseToAnyPublisher() + } + .mapError { error in + // Prevent nesting the 'ranOutOfRandomSnodes' errors + switch error { + case SnodeAPIError.ranOutOfRandomSnodes: break + default: lastError = error + } + + return error + } + .retry(retries) + .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + } +} + +// MARK: - SnodeAPI Cache + +public extension Network.SnodeAPI { + class Cache: SnodeAPICacheType { + private let dependencies: Dependencies + public var hardfork: Int + public var softfork: Int + public var clockOffsetMs: Int64 = 0 + + init(using dependencies: Dependencies) { + self.dependencies = dependencies + self.hardfork = dependencies[defaults: .standard, key: .hardfork] + self.softfork = dependencies[defaults: .standard, key: .softfork] + } + + public func currentOffsetTimestampMs() -> T { + let timestampNowMs: Int64 = (Int64(floor(dependencies.dateNow.timeIntervalSince1970 * 1000)) + clockOffsetMs) + + guard let convertedTimestampNowMs: T = T(exactly: timestampNowMs) else { + Log.critical("[SnodeAPI.Cache] Failed to convert the timestamp to the desired type: \(type(of: T.self)).") + return 0 + } + + return convertedTimestampNowMs + } + + public func setClockOffsetMs(_ clockOffsetMs: Int64) { + self.clockOffsetMs = clockOffsetMs + } + } +} + +public extension Cache { + static let snodeAPI: CacheConfig = Dependencies.create( + identifier: "snodeAPI", + createInstance: { dependencies in Network.SnodeAPI.Cache(using: dependencies) }, + mutableInstance: { $0 }, + immutableInstance: { $0 } + ) +} + +// MARK: - SnodeAPICacheType + +/// This is a read-only version of the Cache designed to avoid unintentionally mutating the instance in a non-thread-safe way +public protocol SnodeAPIImmutableCacheType: ImmutableCacheType { + /// The last seen storage server hard fork version. + var hardfork: Int { get } + + /// The last seen storage server soft fork version. + var softfork: Int { get } + + /// The offset between the user's clock and the Service Node's clock. Used in cases where the + /// user's clock is incorrect. + var clockOffsetMs: Int64 { get } + + /// Tthe current user clock timestamp in milliseconds offset by the difference between the user's clock and the clock of the most + /// recent Service Node's that was communicated with. + func currentOffsetTimestampMs() -> T +} + +public protocol SnodeAPICacheType: SnodeAPIImmutableCacheType, MutableCacheType { + /// The last seen storage server hard fork version. + var hardfork: Int { get set } + + /// The last seen storage server soft fork version. + var softfork: Int { get set } + + /// A function to update the offset between the user's clock and the Service Node's clock. + func setClockOffsetMs(_ clockOffsetMs: Int64) +} diff --git a/SessionSnodeKit/SnodeAPI/SnodeAPIEndpoint.swift b/SessionNetworkingKit/StorageServer/SnodeAPIEndpoint.swift similarity index 95% rename from SessionSnodeKit/SnodeAPI/SnodeAPIEndpoint.swift rename to SessionNetworkingKit/StorageServer/SnodeAPIEndpoint.swift index 45cec1c60e..6bd1f1b1a9 100644 --- a/SessionSnodeKit/SnodeAPI/SnodeAPIEndpoint.swift +++ b/SessionNetworkingKit/StorageServer/SnodeAPIEndpoint.swift @@ -3,9 +3,8 @@ // stringlint:disable import Foundation -import SessionUtilitiesKit -public extension SnodeAPI { +public extension Network.SnodeAPI { enum Endpoint: EndpointType { case sendMessage case getMessages @@ -35,7 +34,7 @@ public extension SnodeAPI { case daemonOnsResolve case daemonGetServiceNodes - public static var name: String { "SnodeAPI.Endpoint" } + public static var name: String { "StorageServer.Endpoint" } public static var batchRequestVariant: Network.BatchRequest.Child.Variant = .storageServer public var path: String { diff --git a/SessionSnodeKit/SnodeAPI/SnodeAPIError.swift b/SessionNetworkingKit/StorageServer/SnodeAPIError.swift similarity index 99% rename from SessionSnodeKit/SnodeAPI/SnodeAPIError.swift rename to SessionNetworkingKit/StorageServer/SnodeAPIError.swift index 0de7eb2ba8..c09b5ed963 100644 --- a/SessionSnodeKit/SnodeAPI/SnodeAPIError.swift +++ b/SessionNetworkingKit/StorageServer/SnodeAPIError.swift @@ -3,7 +3,6 @@ // stringlint:disable import Foundation -import SessionUtilitiesKit public enum SnodeAPIError: Error, CustomStringConvertible { case clockOutOfSync diff --git a/SessionSnodeKit/SnodeAPI/SnodeAPINamespace.swift b/SessionNetworkingKit/StorageServer/SnodeAPINamespace.swift similarity index 99% rename from SessionSnodeKit/SnodeAPI/SnodeAPINamespace.swift rename to SessionNetworkingKit/StorageServer/SnodeAPINamespace.swift index c6b23c1e75..ea3399d543 100644 --- a/SessionSnodeKit/SnodeAPI/SnodeAPINamespace.swift +++ b/SessionNetworkingKit/StorageServer/SnodeAPINamespace.swift @@ -6,7 +6,7 @@ import Foundation import SessionUtil import SessionUtilitiesKit -public extension SnodeAPI { +public extension Network.SnodeAPI { enum Namespace: Int, Codable, Hashable, CustomStringConvertible { /// Messages sent to one-to-one conversations are stored in this namespace case `default` = 0 diff --git a/SessionSnodeKit/SnodeAPI/Request+SnodeAPI.swift b/SessionNetworkingKit/StorageServer/Types/Request+SnodeAPI.swift similarity index 86% rename from SessionSnodeKit/SnodeAPI/Request+SnodeAPI.swift rename to SessionNetworkingKit/StorageServer/Types/Request+SnodeAPI.swift index 48914fdd90..4f2c50e84f 100644 --- a/SessionSnodeKit/SnodeAPI/Request+SnodeAPI.swift +++ b/SessionNetworkingKit/StorageServer/Types/Request+SnodeAPI.swift @@ -5,11 +5,9 @@ import Foundation import SessionUtilitiesKit -// MARK: Request - SnodeAPI - -public extension Request where Endpoint == SnodeAPI.Endpoint { +public extension Request where Endpoint == Network.SnodeAPI.Endpoint { init( - endpoint: SnodeAPI.Endpoint, + endpoint: Endpoint, snode: LibSession.Snode, swarmPublicKey: String? = nil, body: B @@ -28,10 +26,10 @@ public extension Request where Endpoint == SnodeAPI.Endpoint { } init( - endpoint: SnodeAPI.Endpoint, + endpoint: Endpoint, swarmPublicKey: String, body: B, - snodeRetrievalRetryCount: Int = SnodeAPI.maxRetryCount + snodeRetrievalRetryCount: Int = Network.SnodeAPI.maxRetryCount ) throws where T == SnodeRequest { self = try Request( endpoint: endpoint, @@ -51,7 +49,7 @@ public extension Request where Endpoint == SnodeAPI.Endpoint { swarmPublicKey: String, requiresLatestNetworkTime: Bool, body: B, - snodeRetrievalRetryCount: Int = SnodeAPI.maxRetryCount + snodeRetrievalRetryCount: Int = Network.SnodeAPI.maxRetryCount ) throws where T == SnodeRequest, B: Encodable & UpdatableTimestamp { self = try Request( endpoint: endpoint, diff --git a/SessionSnodeKit/SnodeAPI/ResponseInfo+SnodeAPI.swift b/SessionNetworkingKit/StorageServer/Types/ResponseInfo+SnodeAPI.swift similarity index 93% rename from SessionSnodeKit/SnodeAPI/ResponseInfo+SnodeAPI.swift rename to SessionNetworkingKit/StorageServer/Types/ResponseInfo+SnodeAPI.swift index 91d81a1aba..1c821429aa 100644 --- a/SessionSnodeKit/SnodeAPI/ResponseInfo+SnodeAPI.swift +++ b/SessionNetworkingKit/StorageServer/Types/ResponseInfo+SnodeAPI.swift @@ -3,7 +3,7 @@ import Foundation import SessionUtilitiesKit -public extension SnodeAPI { +public extension Network.SnodeAPI { struct LatestTimestampResponseInfo: ResponseInfoType { public let code: Int public let headers: [String: String] diff --git a/SessionSnodeKit/Models/SnodeMessage.swift b/SessionNetworkingKit/StorageServer/Types/SnodeMessage.swift similarity index 100% rename from SessionSnodeKit/Models/SnodeMessage.swift rename to SessionNetworkingKit/StorageServer/Types/SnodeMessage.swift diff --git a/SessionSnodeKit/Models/SnodeReceivedMessage.swift b/SessionNetworkingKit/StorageServer/Types/SnodeReceivedMessage.swift similarity index 95% rename from SessionSnodeKit/Models/SnodeReceivedMessage.swift rename to SessionNetworkingKit/StorageServer/Types/SnodeReceivedMessage.swift index 99604b49e8..ad2384fbbb 100644 --- a/SessionSnodeKit/Models/SnodeReceivedMessage.swift +++ b/SessionNetworkingKit/StorageServer/Types/SnodeReceivedMessage.swift @@ -15,7 +15,7 @@ public struct SnodeReceivedMessage: Codable, CustomDebugStringConvertible { public let snode: LibSession.Snode? public let swarmPublicKey: String - public let namespace: SnodeAPI.Namespace + public let namespace: Network.SnodeAPI.Namespace public let hash: String public let timestampMs: Int64 public let expirationTimestampMs: Int64 @@ -36,7 +36,7 @@ public struct SnodeReceivedMessage: Codable, CustomDebugStringConvertible { public init?( snode: LibSession.Snode?, publicKey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, rawMessage: GetMessagesResponse.RawMessage ) { guard let data: Data = Data(base64Encoded: rawMessage.base64EncodedDataString) else { diff --git a/SessionSnodeKit/Types/BatchRequest.swift b/SessionNetworkingKit/Types/BatchRequest.swift similarity index 100% rename from SessionSnodeKit/Types/BatchRequest.swift rename to SessionNetworkingKit/Types/BatchRequest.swift diff --git a/SessionSnodeKit/Types/BatchResponse.swift b/SessionNetworkingKit/Types/BatchResponse.swift similarity index 100% rename from SessionSnodeKit/Types/BatchResponse.swift rename to SessionNetworkingKit/Types/BatchResponse.swift diff --git a/SessionSnodeKit/Types/BencodeResponse.swift b/SessionNetworkingKit/Types/BencodeResponse.swift similarity index 100% rename from SessionSnodeKit/Types/BencodeResponse.swift rename to SessionNetworkingKit/Types/BencodeResponse.swift diff --git a/SessionSnodeKit/Types/ContentProxy.swift b/SessionNetworkingKit/Types/ContentProxy.swift similarity index 100% rename from SessionSnodeKit/Types/ContentProxy.swift rename to SessionNetworkingKit/Types/ContentProxy.swift diff --git a/SessionSnodeKit/Types/Destination.swift b/SessionNetworkingKit/Types/Destination.swift similarity index 100% rename from SessionSnodeKit/Types/Destination.swift rename to SessionNetworkingKit/Types/Destination.swift diff --git a/SessionSnodeKit/Types/HTTPHeader.swift b/SessionNetworkingKit/Types/HTTPHeader.swift similarity index 100% rename from SessionSnodeKit/Types/HTTPHeader.swift rename to SessionNetworkingKit/Types/HTTPHeader.swift diff --git a/SessionSnodeKit/Types/HTTPMethod.swift b/SessionNetworkingKit/Types/HTTPMethod.swift similarity index 100% rename from SessionSnodeKit/Types/HTTPMethod.swift rename to SessionNetworkingKit/Types/HTTPMethod.swift diff --git a/SessionSnodeKit/Types/HTTPQueryParam.swift b/SessionNetworkingKit/Types/HTTPQueryParam.swift similarity index 100% rename from SessionSnodeKit/Types/HTTPQueryParam.swift rename to SessionNetworkingKit/Types/HTTPQueryParam.swift diff --git a/SessionSnodeKit/Types/IPv4.swift b/SessionNetworkingKit/Types/IPv4.swift similarity index 100% rename from SessionSnodeKit/Types/IPv4.swift rename to SessionNetworkingKit/Types/IPv4.swift diff --git a/SessionSnodeKit/Types/JSON.swift b/SessionNetworkingKit/Types/JSON.swift similarity index 100% rename from SessionSnodeKit/Types/JSON.swift rename to SessionNetworkingKit/Types/JSON.swift diff --git a/SessionNetworkingKit/Types/Network.swift b/SessionNetworkingKit/Types/Network.swift new file mode 100644 index 0000000000..a11afeb54c --- /dev/null +++ b/SessionNetworkingKit/Types/Network.swift @@ -0,0 +1,53 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import Combine +import SessionUtilitiesKit + +// MARK: - Singleton + +public extension Singleton { + static let network: SingletonConfig = Dependencies.create( + identifier: "network", + createInstance: { dependencies in LibSessionNetwork(using: dependencies) } + ) +} + +// MARK: - Network Constants + +public class Network { + public static let defaultTimeout: TimeInterval = 10 + public static let fileUploadTimeout: TimeInterval = 60 + public static let fileDownloadTimeout: TimeInterval = 30 + + /// **Note:** The max file size is 10,000,000 bytes (rather than 10MiB which would be `(10 * 1024 * 1024)`), 10,000,000 + /// exactly will be fine but a single byte more will result in an error + public static let maxFileSize: UInt = 10_000_000 +} + +// MARK: - NetworkStatus + +public enum NetworkStatus { + case unknown + case connecting + case connected + case disconnected +} + +// MARK: - NetworkType + +public protocol NetworkType { + func getSwarm(for swarmPublicKey: String) -> AnyPublisher, Error> + func getRandomNodes(count: Int) -> AnyPublisher, Error> + + func send( + _ body: Data?, + to destination: Network.Destination, + requestTimeout: TimeInterval, + requestAndPathBuildTimeout: TimeInterval? + ) -> AnyPublisher<(ResponseInfoType, Data?), Error> + + func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, Network.FileServer.AppVersionResponse), Error> +} diff --git a/SessionSnodeKit/Types/NetworkError.swift b/SessionNetworkingKit/Types/NetworkError.swift similarity index 100% rename from SessionSnodeKit/Types/NetworkError.swift rename to SessionNetworkingKit/Types/NetworkError.swift diff --git a/SessionSnodeKit/Types/PreparedRequest+Sending.swift b/SessionNetworkingKit/Types/PreparedRequest+Sending.swift similarity index 100% rename from SessionSnodeKit/Types/PreparedRequest+Sending.swift rename to SessionNetworkingKit/Types/PreparedRequest+Sending.swift diff --git a/SessionSnodeKit/Types/PreparedRequest.swift b/SessionNetworkingKit/Types/PreparedRequest.swift similarity index 100% rename from SessionSnodeKit/Types/PreparedRequest.swift rename to SessionNetworkingKit/Types/PreparedRequest.swift diff --git a/SessionSnodeKit/Types/ProxiedContentDownloader.swift b/SessionNetworkingKit/Types/ProxiedContentDownloader.swift similarity index 100% rename from SessionSnodeKit/Types/ProxiedContentDownloader.swift rename to SessionNetworkingKit/Types/ProxiedContentDownloader.swift diff --git a/SessionSnodeKit/Types/Request.swift b/SessionNetworkingKit/Types/Request.swift similarity index 100% rename from SessionSnodeKit/Types/Request.swift rename to SessionNetworkingKit/Types/Request.swift diff --git a/SessionNetworkingKit/Types/RequestCategory.swift b/SessionNetworkingKit/Types/RequestCategory.swift new file mode 100644 index 0000000000..e69de29bb2 diff --git a/SessionSnodeKit/Types/ResponseInfo.swift b/SessionNetworkingKit/Types/ResponseInfo.swift similarity index 100% rename from SessionSnodeKit/Types/ResponseInfo.swift rename to SessionNetworkingKit/Types/ResponseInfo.swift diff --git a/SessionNetworkingKit/Types/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift b/SessionNetworkingKit/Types/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift new file mode 100644 index 0000000000..464a3b1b9e --- /dev/null +++ b/SessionNetworkingKit/Types/SessionNetworkAPI/HTTPHeader+SessionNetwork.swift @@ -0,0 +1,9 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +public extension HTTPHeader { + static let tokenServerPubKey: HTTPHeader = "X-FS-Pubkey" + static let tokenServerTimestamp: HTTPHeader = "X-FS-Timestamp" + static let tokenServerSignature: HTTPHeader = "X-FS-Signature" +} diff --git a/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Database.swift b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Database.swift new file mode 100644 index 0000000000..9316b1a440 --- /dev/null +++ b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Database.swift @@ -0,0 +1,32 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import AudioToolbox +import GRDB +import DifferenceKit +import SessionUtilitiesKit + +public extension KeyValueStore.StringKey { + static let contractAddress: KeyValueStore.StringKey = "contractAddress" +} + +public extension KeyValueStore.DoubleKey { + static let tokenUsd: KeyValueStore.DoubleKey = "tokenUsd" + static let marketCapUsd: KeyValueStore.DoubleKey = "marketCapUsd" + static let stakingRequirement: KeyValueStore.DoubleKey = "stakingRequirement" + static let stakingRewardPool: KeyValueStore.DoubleKey = "stakingRewardPool" + static let networkStakedTokens: KeyValueStore.DoubleKey = "networkStakedTokens" + static let networkStakedUSD: KeyValueStore.DoubleKey = "networkStakedUSD" +} + +public extension KeyValueStore.IntKey { + static let networkSize: KeyValueStore.IntKey = "networkSize" +} + +public extension KeyValueStore.Int64Key { + static let lastUpdatedTimestampMs: KeyValueStore.Int64Key = "lastUpdatedTimestampMs" + static let staleTimestampMs: KeyValueStore.Int64Key = "staleTimestampMs" + static let priceTimestampMs: KeyValueStore.Int64Key = "priceTimestampMs" +} diff --git a/SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Models.swift b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Models.swift similarity index 100% rename from SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Models.swift rename to SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Models.swift diff --git a/SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Network.swift b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Network.swift similarity index 92% rename from SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Network.swift rename to SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Network.swift index fb26688ac1..947e55d15d 100644 --- a/SessionSnodeKit/SessionNetworkAPI/SessionNetworkAPI+Network.swift +++ b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI+Network.swift @@ -41,12 +41,15 @@ extension SessionNetworkAPI { .eraseToAnyPublisher() } - return Result { - try SessionNetworkAPI - .prepareInfo(using: dependencies) + return dependencies[singleton: .storage] + .readPublisher { db -> Network.PreparedRequest in + try SessionNetworkAPI + .prepareInfo( + db, + using: dependencies + ) } - .publisher - .flatMap { [dependencies] in $0.send(using: dependencies) } + .flatMap { $0.send(using: dependencies) } .map { _, info in info } .flatMapStorageWritePublisher(using: dependencies) { [dependencies] db, info -> Bool in // Token info diff --git a/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI.swift b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI.swift new file mode 100644 index 0000000000..1d1891a51d --- /dev/null +++ b/SessionNetworkingKit/Types/SessionNetworkAPI/SessionNetworkAPI.swift @@ -0,0 +1,131 @@ +// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import Combine +import GRDB +import SessionUtilitiesKit + +public enum SessionNetworkAPI { + public static let workQueue = DispatchQueue(label: "SessionNetworkAPI.workQueue", qos: .userInitiated) + public static let client = HTTPClient() + + // MARK: - Info + + /// General token info. This endpoint combines the `/price` and `/token` endpoint information. + /// + /// `GET/info` + + public static func prepareInfo( + _ db: Database, + using dependencies: Dependencies + ) throws -> Network.PreparedRequest { + return try Network.PreparedRequest( + request: Request( + endpoint: Network.NetworkAPI.Endpoint.info, + destination: .server( + method: .get, + server: Network.NetworkAPI.networkAPIServer, + queryParameters: [:], + x25519PublicKey: Network.NetworkAPI.networkAPIServerPublicKey + ) + ), + responseType: Info.self, + requestAndPathBuildTimeout: Network.defaultTimeout, + using: dependencies + ) + .signed(db, with: SessionNetworkAPI.signRequest, using: dependencies) + } + + // MARK: - Authentication + + fileprivate static func signatureHeaders( + _ db: Database, + url: URL, + method: HTTPMethod, + body: Data?, + using dependencies: Dependencies + ) throws -> [HTTPHeader: String] { + let timestamp: UInt64 = UInt64(floor(dependencies.dateNow.timeIntervalSince1970)) + let path: String = url.path + .appending(url.query.map { value in "?\(value)" }) + + let signResult: (publicKey: String, signature: [UInt8]) = try sign( + db, + timestamp: timestamp, + method: method.rawValue, + path: path, + body: body, + using: dependencies + ) + + return [ + HTTPHeader.tokenServerPubKey: signResult.publicKey, + HTTPHeader.tokenServerTimestamp: "\(timestamp)", + HTTPHeader.tokenServerSignature: signResult.signature.toBase64() + ] + } + + private static func sign( + _ db: Database, + timestamp: UInt64, + method: String, + path: String, + body: Data?, + using dependencies: Dependencies + ) throws -> (publicKey: String, signature: [UInt8]) { + let bodyString: String? = { + guard let bodyData: Data = body else { return nil } + return String(data: bodyData, encoding: .utf8) + }() + + guard + let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db), + let blinded07KeyPair: KeyPair = dependencies[singleton: .crypto].generate( + .versionBlinded07KeyPair(ed25519SecretKey: userEdKeyPair.secretKey) + ), + let signatureResult: [UInt8] = dependencies[singleton: .crypto].generate( + .signatureVersionBlind07( + timestamp: timestamp, + method: method, + path: path, + body: bodyString, + ed25519SecretKey: userEdKeyPair.secretKey + ) + ) + else { throw NetworkError.signingFailed } + + return ( + publicKey: SessionId(.versionBlinded07, publicKey: blinded07KeyPair.publicKey).hexString, + signature: signatureResult + ) + } + + private static func signRequest( + _ db: Database, + preparedRequest: Network.PreparedRequest, + using dependencies: Dependencies + ) throws -> Network.Destination { + guard let url: URL = preparedRequest.destination.url else { + throw NetworkError.signingFailed + } + + guard case let .server(info) = preparedRequest.destination else { + throw NetworkError.signingFailed + } + + return .server( + info: info.updated( + with: try signatureHeaders( + db, + url: url, + method: preparedRequest.method, + body: preparedRequest.body, + using: dependencies + ) + ) + ) + } +} + diff --git a/SessionSnodeKit/Types/SwarmDrainBehaviour.swift b/SessionNetworkingKit/Types/SwarmDrainBehaviour.swift similarity index 100% rename from SessionSnodeKit/Types/SwarmDrainBehaviour.swift rename to SessionNetworkingKit/Types/SwarmDrainBehaviour.swift diff --git a/SessionSnodeKit/Types/UpdatableTimestamp.swift b/SessionNetworkingKit/Types/UpdatableTimestamp.swift similarity index 100% rename from SessionSnodeKit/Types/UpdatableTimestamp.swift rename to SessionNetworkingKit/Types/UpdatableTimestamp.swift diff --git a/SessionSnodeKit/Types/ValidatableResponse.swift b/SessionNetworkingKit/Types/ValidatableResponse.swift similarity index 100% rename from SessionSnodeKit/Types/ValidatableResponse.swift rename to SessionNetworkingKit/Types/ValidatableResponse.swift diff --git a/SessionSnodeKit/Utilities/Data+Utilities.swift b/SessionNetworkingKit/Utilities/Data+Utilities.swift similarity index 100% rename from SessionSnodeKit/Utilities/Data+Utilities.swift rename to SessionNetworkingKit/Utilities/Data+Utilities.swift diff --git a/SessionNetworkingKit/Utilities/ObservableKey+SessionNetworkingKit.swift b/SessionNetworkingKit/Utilities/ObservableKey+SessionNetworkingKit.swift new file mode 100644 index 0000000000..372aaf717d --- /dev/null +++ b/SessionNetworkingKit/Utilities/ObservableKey+SessionNetworkingKit.swift @@ -0,0 +1,23 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. +// +// stringlint:disable + +import Foundation +import SessionUtilitiesKit + +public extension ObservableKey { + static func networkLifecycle(_ event: NetworkLifecycle) -> ObservableKey { + ObservableKey("networkLifecycle-\(event)", .networkLifecycle) + } +} + +public extension GenericObservableKey { + static let networkLifecycle: GenericObservableKey = "networkLifecycle" +} + +// MARK: - NetworkLifecycle + +public enum NetworkLifecycle: String, Sendable { + case suspended + case resumed +} diff --git a/SessionSnodeKit/Utilities/Publisher+Utilities.swift b/SessionNetworkingKit/Utilities/Publisher+Utilities.swift similarity index 100% rename from SessionSnodeKit/Utilities/Publisher+Utilities.swift rename to SessionNetworkingKit/Utilities/Publisher+Utilities.swift diff --git a/SessionSnodeKit/Utilities/RetryWithDependencies.swift b/SessionNetworkingKit/Utilities/RetryWithDependencies.swift similarity index 100% rename from SessionSnodeKit/Utilities/RetryWithDependencies.swift rename to SessionNetworkingKit/Utilities/RetryWithDependencies.swift diff --git a/SessionSnodeKit/Utilities/String+Trimming.swift b/SessionNetworkingKit/Utilities/String+Trimming.swift similarity index 100% rename from SessionSnodeKit/Utilities/String+Trimming.swift rename to SessionNetworkingKit/Utilities/String+Trimming.swift diff --git a/SessionSnodeKit/Utilities/URLResponse+Utilities.swift b/SessionNetworkingKit/Utilities/URLResponse+Utilities.swift similarity index 100% rename from SessionSnodeKit/Utilities/URLResponse+Utilities.swift rename to SessionNetworkingKit/Utilities/URLResponse+Utilities.swift diff --git a/SessionSnodeKitTests/Models/FileUploadResponseSpec.swift b/SessionNetworkingKitTests/Models/FileUploadResponseSpec.swift similarity index 96% rename from SessionSnodeKitTests/Models/FileUploadResponseSpec.swift rename to SessionNetworkingKitTests/Models/FileUploadResponseSpec.swift index 1454fbe383..4339984b89 100644 --- a/SessionSnodeKitTests/Models/FileUploadResponseSpec.swift +++ b/SessionNetworkingKitTests/Models/FileUploadResponseSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class FileUploadResponseSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Models/SnodeRequestSpec.swift b/SessionNetworkingKitTests/Models/SnodeRequestSpec.swift similarity index 98% rename from SessionSnodeKitTests/Models/SnodeRequestSpec.swift rename to SessionNetworkingKitTests/Models/SnodeRequestSpec.swift index 3d6836709b..0405d71e7c 100644 --- a/SessionSnodeKitTests/Models/SnodeRequestSpec.swift +++ b/SessionNetworkingKitTests/Models/SnodeRequestSpec.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class SnodeRequestSpec: QuickSpec { override class func spec() { diff --git a/SessionNetworkingKitTests/SOGS/Crypto/Authentication+SOGS.swift b/SessionNetworkingKitTests/SOGS/Crypto/Authentication+SOGS.swift new file mode 100644 index 0000000000..92862325b1 --- /dev/null +++ b/SessionNetworkingKitTests/SOGS/Crypto/Authentication+SOGS.swift @@ -0,0 +1,50 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +// MARK: - Authentication Types + +public extension Authentication { + /// Used when interacting with a community + struct community: AuthenticationMethod { + public let roomToken: String + public let server: String + public let publicKey: String + public let hasCapabilities: Bool + public let supportsBlinding: Bool + public let forceBlinded: Bool + + public var info: Info { + .community( + server: server, + publicKey: publicKey, + hasCapabilities: hasCapabilities, + supportsBlinding: supportsBlinding, + forceBlinded: forceBlinded + ) + } + + public init( + roomToken: String, + server: String, + publicKey: String, + hasCapabilities: Bool, + supportsBlinding: Bool, + forceBlinded: Bool = false + ) { + self.roomToken = roomToken + self.server = server + self.publicKey = publicKey + self.hasCapabilities = hasCapabilities + self.supportsBlinding = supportsBlinding + self.forceBlinded = forceBlinded + } + + // MARK: - SignatureGenerator + + public func generateSignature(with verificationBytes: [UInt8], using dependencies: Dependencies) throws -> Authentication.Signature { + throw CryptoError.signatureGenerationFailed + } + } +} diff --git a/SessionNetworkingKitTests/SOGS/Crypto/CryptoSOGSAPISpec.swift b/SessionNetworkingKitTests/SOGS/Crypto/CryptoSOGSAPISpec.swift new file mode 100644 index 0000000000..d141b5549f --- /dev/null +++ b/SessionNetworkingKitTests/SOGS/Crypto/CryptoSOGSAPISpec.swift @@ -0,0 +1,180 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +import Quick +import Nimble + +@testable import SessionNetworkingKit + +class CryptoSOGSAPISpec: QuickSpec { + override class func spec() { + // MARK: Configuration + + @TestState var dependencies: TestDependencies! = TestDependencies() + @TestState(singleton: .crypto, in: dependencies) var crypto: Crypto! = Crypto(using: dependencies) + @TestState(cache: .general, in: dependencies) var mockGeneralCache: MockGeneralCache! = MockGeneralCache( + initialSetup: { cache in + cache.when { $0.sessionId }.thenReturn(SessionId(.standard, hex: TestConstants.publicKey)) + cache.when { $0.ed25519SecretKey }.thenReturn(Array(Data(hex: TestConstants.edSecretKey))) + } + ) + + // MARK: - Crypto for SOGSAPI + describe("Crypto for SOGSAPI") { + // MARK: -- when generating a blinded15 key pair + context("when generating a blinded15 key pair") { + // MARK: ---- successfully generates + it("successfully generates") { + let result = crypto.generate( + .blinded15KeyPair( + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes + ) + ) + + // Note: The first 64 characters of the secretKey are consistent but the chars after that always differ + expect(result?.publicKey.toHexString()).to(equal(TestConstants.blind15PublicKey)) + expect(result?.secretKey.toHexString()).to(equal(TestConstants.blind15SecretKey)) + } + + // MARK: ---- fails if the edKeyPair secret key length wrong + it("fails if the ed25519SecretKey length wrong") { + let result = crypto.generate( + .blinded15KeyPair( + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Array(Data(hex: String(TestConstants.edSecretKey.prefix(4)))) + ) + ) + + expect(result).to(beNil()) + } + } + + // MARK: -- when generating a blinded25 key pair + context("when generating a blinded25 key pair") { + // MARK: ---- successfully generates + it("successfully generates") { + let result = crypto.generate( + .blinded25KeyPair( + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes + ) + ) + + // Note: The first 64 characters of the secretKey are consistent but the chars after that always differ + expect(result?.publicKey.toHexString()).to(equal(TestConstants.blind25PublicKey)) + expect(result?.secretKey.toHexString()).to(equal(TestConstants.blind25SecretKey)) + } + + // MARK: ---- fails if the edKeyPair secret key length wrong + it("fails if the ed25519SecretKey length wrong") { + let result = crypto.generate( + .blinded25KeyPair( + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Data(hex: String(TestConstants.edSecretKey.prefix(4))).bytes + ) + ) + + expect(result).to(beNil()) + } + } + + // MARK: -- when generating a signatureBlind15 + context("when generating a signatureBlind15") { + // MARK: ---- generates a correct signature + it("generates a correct signature") { + let result = crypto.generate( + .signatureBlind15( + message: "TestMessage".bytes, + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Array(Data(hex: TestConstants.edSecretKey)) + ) + ) + + expect(result?.toHexString()) + .to(equal( + "245003f1627ebdfc6099c32597d426ef84d1b301861a5ffbbac92dde6c608334" + + "ceb56a022a094a9a664fae034b50eed40bd1bfb262c7e542c979eec265ae3f07" + )) + } + } + + // MARK: -- when generating a signatureBlind25 + context("when generating a signatureBlind25") { + // MARK: ---- generates a correct signature + it("generates a correct signature") { + let result = crypto.generate( + .signatureBlind25( + message: "TestMessage".bytes, + serverPublicKey: TestConstants.serverPublicKey, + ed25519SecretKey: Data(hex: TestConstants.edSecretKey).bytes + ) + ) + + expect(result?.toHexString()) + .to(equal( + "9ff9b7fb7d435c7a2c0b0b2ae64963baaf394386b9f7c7f924eeac44ec0f74c7" + + "fe6304c73a9b3a65491f81e44b545e54631e83e9a412eaed5fd4db2e05ec830c" + )) + } + } + + // MARK: -- when checking if a session id matches a blinded id + context("when checking if a session id matches a blinded id") { + // MARK: ---- returns true when a blind15 id matches + it("returns true when a blind15 id matches") { + let result = crypto.verify( + .sessionId( + "05\(TestConstants.publicKey)", + matchesBlindedId: "15\(TestConstants.blind15PublicKey)", + serverPublicKey: TestConstants.serverPublicKey + ) + ) + + expect(result).to(beTrue()) + } + + // MARK: ---- returns true when a blind25 id matches + it("returns true when a blind25 id matches") { + let result = crypto.verify( + .sessionId( + "05\(TestConstants.publicKey)", + matchesBlindedId: "25\(TestConstants.blind25PublicKey)", + serverPublicKey: TestConstants.serverPublicKey + ) + ) + + expect(result).to(beTrue()) + } + + // MARK: ---- returns false if given an invalid session id + it("returns false if given an invalid session id") { + let result = crypto.verify( + .sessionId( + "AB\(TestConstants.publicKey)", + matchesBlindedId: "15\(TestConstants.blind15PublicKey)", + serverPublicKey: TestConstants.serverPublicKey + ) + ) + + expect(result).to(beFalse()) + } + + // MARK: ---- returns false if given an invalid blinded id + it("returns false if given an invalid blinded id") { + let result = crypto.verify( + .sessionId( + "05\(TestConstants.publicKey)", + matchesBlindedId: "AB\(TestConstants.blind15PublicKey)", + serverPublicKey: TestConstants.serverPublicKey + ) + ) + + expect(result).to(beFalse()) + } + } + } + } +} diff --git a/SessionNetworkingKitTests/SOGS/Models/CapabilitiesResponse.swift b/SessionNetworkingKitTests/SOGS/Models/CapabilitiesResponse.swift new file mode 100644 index 0000000000..4b6ee85475 --- /dev/null +++ b/SessionNetworkingKitTests/SOGS/Models/CapabilitiesResponse.swift @@ -0,0 +1,38 @@ +// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +import Quick +import Nimble + +@testable import SessionNetworkingKit + +class CapabilitiesResponseSpec: QuickSpec { + override class func spec() { + // MARK: - CapabilitiesResponse + describe("CapabilitiesResponse") { + // MARK: -- when initializing + context("when initializing") { + // MARK: ---- assigns values correctly + it("assigns values correctly") { + let capabilities: Network.SOGS.CapabilitiesResponse = Network.SOGS.CapabilitiesResponse( + capabilities: ["sogs"], + missing: ["test"] + ) + + expect(capabilities.capabilities).to(equal(["sogs"])) + expect(capabilities.missing).to(equal(["test"])) + } + + it("defaults missing to nil") { + let capabilities: Network.SOGS.CapabilitiesResponse = Network.SOGS.CapabilitiesResponse( + capabilities: ["sogs"] + ) + + expect(capabilities.capabilities).to(equal(["sogs"])) + expect(capabilities.missing).to(beNil()) + } + } + } + } +} diff --git a/SessionMessagingKitTests/Open Groups/Models/RoomPollInfoSpec.swift b/SessionNetworkingKitTests/SOGS/Models/RoomPollInfoSpec.swift similarity index 92% rename from SessionMessagingKitTests/Open Groups/Models/RoomPollInfoSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/RoomPollInfoSpec.swift index 386f2f02bc..f088a86205 100644 --- a/SessionMessagingKitTests/Open Groups/Models/RoomPollInfoSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/RoomPollInfoSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class RoomPollInfoSpec: QuickSpec { override class func spec() { @@ -15,7 +15,7 @@ class RoomPollInfoSpec: QuickSpec { context("when initializing with a room") { // MARK: ---- copies all the relevant values across it("copies all the relevant values across") { - let room: OpenGroupAPI.Room = OpenGroupAPI.Room( + let room: Network.SOGS.Room = Network.SOGS.Room( token: "testToken", name: "testName", roomDescription: nil, @@ -42,7 +42,7 @@ class RoomPollInfoSpec: QuickSpec { upload: true, defaultUpload: true ) - let roomPollInfo: OpenGroupAPI.RoomPollInfo = OpenGroupAPI.RoomPollInfo(room: room) + let roomPollInfo: Network.SOGS.RoomPollInfo = Network.SOGS.RoomPollInfo(room: room) expect(roomPollInfo.token).to(equal(room.token)) expect(roomPollInfo.activeUsers).to(equal(room.activeUsers)) @@ -82,7 +82,7 @@ class RoomPollInfoSpec: QuickSpec { } """ let roomData: Data = roomPollInfoJson.data(using: .utf8)! - let result: OpenGroupAPI.RoomPollInfo = try! JSONDecoder().decode(OpenGroupAPI.RoomPollInfo.self, from: roomData) + let result: Network.SOGS.RoomPollInfo = try! JSONDecoder().decode(Network.SOGS.RoomPollInfo.self, from: roomData) expect(result.admin).to(beFalse()) expect(result.globalAdmin).to(beFalse()) @@ -115,7 +115,7 @@ class RoomPollInfoSpec: QuickSpec { } """ let roomData: Data = roomPollInfoJson.data(using: .utf8)! - let result: OpenGroupAPI.RoomPollInfo = try! JSONDecoder().decode(OpenGroupAPI.RoomPollInfo.self, from: roomData) + let result: Network.SOGS.RoomPollInfo = try! JSONDecoder().decode(Network.SOGS.RoomPollInfo.self, from: roomData) expect(result.admin).to(beTrue()) expect(result.globalAdmin).to(beTrue()) diff --git a/SessionMessagingKitTests/Open Groups/Models/RoomSpec.swift b/SessionNetworkingKitTests/SOGS/Models/RoomSpec.swift similarity index 93% rename from SessionMessagingKitTests/Open Groups/Models/RoomSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/RoomSpec.swift index 2fd43d679b..2238a9e021 100644 --- a/SessionMessagingKitTests/Open Groups/Models/RoomSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/RoomSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class RoomSpec: QuickSpec { override class func spec() { @@ -45,7 +45,7 @@ class RoomSpec: QuickSpec { } """ let roomData: Data = roomJson.data(using: .utf8)! - let result: OpenGroupAPI.Room = try! JSONDecoder().decode(OpenGroupAPI.Room.self, from: roomData) + let result: Network.SOGS.Room = try! JSONDecoder().decode(Network.SOGS.Room.self, from: roomData) expect(result.admin).to(beFalse()) expect(result.globalAdmin).to(beFalse()) @@ -89,7 +89,7 @@ class RoomSpec: QuickSpec { } """ let roomData: Data = roomJson.data(using: .utf8)! - let result: OpenGroupAPI.Room = try! JSONDecoder().decode(OpenGroupAPI.Room.self, from: roomData) + let result: Network.SOGS.Room = try! JSONDecoder().decode(Network.SOGS.Room.self, from: roomData) expect(result.admin).to(beTrue()) expect(result.globalAdmin).to(beTrue()) diff --git a/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift b/SessionNetworkingKitTests/SOGS/Models/SOGSMessageSpec.swift similarity index 92% rename from SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/SOGSMessageSpec.swift index db9aa0df35..315f6e0e33 100644 --- a/SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/SOGSMessageSpec.swift @@ -1,13 +1,12 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit import SessionUtilitiesKit import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class SOGSMessageSpec: QuickSpec { override class func spec() { @@ -45,7 +44,7 @@ class SOGSMessageSpec: QuickSpec { } """ messageData = messageJson.data(using: .utf8)! - let result: OpenGroupAPI.Message? = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData) + let result: Network.SOGS.Message? = try? decoder.decode(Network.SOGS.Message.self, from: messageData) expect(result).toNot(beNil()) expect(result?.whisper).to(beFalse()) @@ -66,7 +65,7 @@ class SOGSMessageSpec: QuickSpec { } """ messageData = messageJson.data(using: .utf8)! - let result: OpenGroupAPI.Message? = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData) + let result: Network.SOGS.Message? = try? decoder.decode(Network.SOGS.Message.self, from: messageData) expect(result).toNot(beNil()) expect(result?.sender).to(beNil()) @@ -94,7 +93,7 @@ class SOGSMessageSpec: QuickSpec { messageData = messageJson.data(using: .utf8)! expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } @@ -117,7 +116,7 @@ class SOGSMessageSpec: QuickSpec { messageData = messageJson.data(using: .utf8)! expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } @@ -140,7 +139,7 @@ class SOGSMessageSpec: QuickSpec { messageData = messageJson.data(using: .utf8)! expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } @@ -150,7 +149,7 @@ class SOGSMessageSpec: QuickSpec { decoder = JSONDecoder() expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(DependenciesError.missingDependencies)) } @@ -173,7 +172,7 @@ class SOGSMessageSpec: QuickSpec { messageData = messageJson.data(using: .utf8)! expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } @@ -204,7 +203,7 @@ class SOGSMessageSpec: QuickSpec { .thenReturn(true) expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .toNot(beNil()) } @@ -215,7 +214,7 @@ class SOGSMessageSpec: QuickSpec { .when { $0.verify(.signature(message: .any, publicKey: .any, signature: .any)) } .thenReturn(true) - _ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData) + _ = try? decoder.decode(Network.SOGS.Message.self, from: messageData) expect(mockCrypto) .to(call(matchingParameters: .all) { @@ -236,7 +235,7 @@ class SOGSMessageSpec: QuickSpec { .thenReturn(false) expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } @@ -251,7 +250,7 @@ class SOGSMessageSpec: QuickSpec { .thenReturn(true) expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .toNot(beNil()) } @@ -262,7 +261,7 @@ class SOGSMessageSpec: QuickSpec { .when { $0.verify(.signatureXed25519(.any, curve25519PublicKey: .any, data: .any)) } .thenReturn(true) - _ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData) + _ = try? decoder.decode(Network.SOGS.Message.self, from: messageData) expect(mockCrypto) .to(call(matchingParameters: .all) { @@ -283,7 +282,7 @@ class SOGSMessageSpec: QuickSpec { .thenReturn(false) expect { - try decoder.decode(OpenGroupAPI.Message.self, from: messageData) + try decoder.decode(Network.SOGS.Message.self, from: messageData) } .to(throwError(NetworkError.parsingFailed)) } diff --git a/SessionMessagingKitTests/Open Groups/Models/SendDirectMessageRequestSpec.swift b/SessionNetworkingKitTests/SOGS/Models/SendDirectMessageRequestSpec.swift similarity index 86% rename from SessionMessagingKitTests/Open Groups/Models/SendDirectMessageRequestSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/SendDirectMessageRequestSpec.swift index 27e96dd206..c7c462f307 100644 --- a/SessionMessagingKitTests/Open Groups/Models/SendDirectMessageRequestSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/SendDirectMessageRequestSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class SendDirectMessageRequestSpec: QuickSpec { override class func spec() { @@ -15,7 +15,7 @@ class SendDirectMessageRequestSpec: QuickSpec { context("when encoding") { // MARK: ---- encodes the data as a base64 string it("encodes the data as a base64 string") { - let request: OpenGroupAPI.SendDirectMessageRequest = OpenGroupAPI.SendDirectMessageRequest( + let request: Network.SOGS.SendDirectMessageRequest = Network.SOGS.SendDirectMessageRequest( message: "TestData".data(using: .utf8)! ) let requestData: Data = try! JSONEncoder().encode(request) diff --git a/SessionMessagingKitTests/Open Groups/Models/SendMessageRequestSpec.swift b/SessionNetworkingKitTests/SOGS/Models/SendSOGSMessageRequestSpec.swift similarity index 82% rename from SessionMessagingKitTests/Open Groups/Models/SendMessageRequestSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/SendSOGSMessageRequestSpec.swift index ec0c15d380..9c00a94671 100644 --- a/SessionMessagingKitTests/Open Groups/Models/SendMessageRequestSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/SendSOGSMessageRequestSpec.swift @@ -5,17 +5,17 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit -class SendMessageRequestSpec: QuickSpec { +class SendSOGSMessageRequestSpec: QuickSpec { override class func spec() { - // MARK: - a SendMessageRequest - describe("a SendMessageRequest") { + // MARK: - a SendSOGSMessageRequest + describe("a SendSOGSMessageRequest") { // MARK: -- when initializing context("when initializing") { // MARK: ---- defaults the optional values to nil it("defaults the optional values to nil") { - let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest( + let request: Network.SOGS.SendSOGSMessageRequest = Network.SOGS.SendSOGSMessageRequest( data: "TestData".data(using: .utf8)!, signature: "TestSignature".data(using: .utf8)! ) @@ -30,7 +30,7 @@ class SendMessageRequestSpec: QuickSpec { context("when encoding") { // MARK: ---- encodes the data as a base64 string it("encodes the data as a base64 string") { - let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest( + let request: Network.SOGS.SendSOGSMessageRequest = Network.SOGS.SendSOGSMessageRequest( data: "TestData".data(using: .utf8)!, signature: "TestSignature".data(using: .utf8)!, whisperTo: nil, @@ -46,7 +46,7 @@ class SendMessageRequestSpec: QuickSpec { // MARK: ---- encodes the signature as a base64 string it("encodes the signature as a base64 string") { - let request: OpenGroupAPI.SendMessageRequest = OpenGroupAPI.SendMessageRequest( + let request: Network.SOGS.SendSOGSMessageRequest = Network.SOGS.SendSOGSMessageRequest( data: "TestData".data(using: .utf8)!, signature: "TestSignature".data(using: .utf8)!, whisperTo: nil, diff --git a/SessionMessagingKitTests/Open Groups/Models/UpdateMessageRequestSpec.swift b/SessionNetworkingKitTests/SOGS/Models/UpdateMessageRequestSpec.swift similarity index 87% rename from SessionMessagingKitTests/Open Groups/Models/UpdateMessageRequestSpec.swift rename to SessionNetworkingKitTests/SOGS/Models/UpdateMessageRequestSpec.swift index 106bd04c52..847cdf7ac2 100644 --- a/SessionMessagingKitTests/Open Groups/Models/UpdateMessageRequestSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Models/UpdateMessageRequestSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class UpdateMessageRequestSpec: QuickSpec { override class func spec() { @@ -15,7 +15,7 @@ class UpdateMessageRequestSpec: QuickSpec { context("when encoding") { // MARK: ---- encodes the data as a base64 string it("encodes the data as a base64 string") { - let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest( + let request: Network.SOGS.UpdateMessageRequest = Network.SOGS.UpdateMessageRequest( data: "TestData".data(using: .utf8)!, signature: "TestSignature".data(using: .utf8)!, fileIds: nil @@ -29,7 +29,7 @@ class UpdateMessageRequestSpec: QuickSpec { // MARK: ---- encodes the signature as a base64 string it("encodes the signature as a base64 string") { - let request: OpenGroupAPI.UpdateMessageRequest = OpenGroupAPI.UpdateMessageRequest( + let request: Network.SOGS.UpdateMessageRequest = Network.SOGS.UpdateMessageRequest( data: "TestData".data(using: .utf8)!, signature: "TestSignature".data(using: .utf8)!, fileIds: nil diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift b/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift similarity index 73% rename from SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift rename to SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift index fa13eeea02..342be00b57 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift +++ b/SessionNetworkingKitTests/SOGS/SOGSAPISpec.swift @@ -3,15 +3,14 @@ import Foundation import Combine import GRDB -import SessionSnodeKit import SessionUtilitiesKit import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit -class OpenGroupAPISpec: QuickSpec { +class SOGSAPISpec: QuickSpec { override class func spec() { // MARK: Configuration @@ -73,24 +72,21 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(Array(Array(Data(hex: TestConstants.edSecretKey)).prefix(upTo: 32))) } ) - @TestState(cache: .libSession, in: dependencies) var mockLibSessionCache: MockLibSessionCache! = MockLibSessionCache( - initialSetup: { $0.defaultInitialSetup() } - ) @TestState var disposables: [AnyCancellable]! = [] @TestState var error: Error? - // MARK: - an OpenGroupAPI - describe("an OpenGroupAPI") { + // MARK: - a SOGSAPI + describe("a SOGSAPI") { // MARK: -- when preparing a poll request context("when preparing a poll request") { - @TestState var preparedRequest: Network.PreparedRequest>? + @TestState var preparedRequest: Network.PreparedRequest>? // MARK: ---- generates the correct request it("generates the correct request") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -98,15 +94,15 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: false, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -116,20 +112,20 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.path).to(equal("/batch")) expect(preparedRequest?.method.rawValue).to(equal("POST")) expect(preparedRequest?.batchEndpoints.count).to(equal(3)) - expect(preparedRequest?.batchEndpoints[test: 0].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(Network.SOGS.Endpoint.self)) .to(equal(.capabilities)) - expect(preparedRequest?.batchEndpoints[test: 1].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomPollInfo("testRoom", 0))) - expect(preparedRequest?.batchEndpoints[test: 2].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 2].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomMessagesRecent("testRoom"))) } // MARK: ---- retrieves recent messages if there was no last message it("retrieves recent messages if there was no last message") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -137,31 +133,31 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: false, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints[test: 2].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 2].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomMessagesRecent("testRoom"))) } // MARK: ---- retrieves recent messages if there was a last message and it has not performed the initial poll and the last message was too long ago it("retrieves recent messages if there was a last message and it has not performed the initial poll and the last message was too long ago") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 121 @@ -169,31 +165,31 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: false, - timeSinceLastPoll: (CommunityPoller.maxInactivityPeriod + 1), + timeSinceLastPoll: (Network.SOGS.maxInactivityPeriodForPolling + 1), authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints[test: 2].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 2].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomMessagesRecent("testRoom"))) } // MARK: ---- retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago it("retrieves recent messages if there was a last message and it has performed an initial poll but it was not too long ago") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 122 @@ -201,31 +197,31 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: false, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints[test: 2].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 2].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomMessagesSince("testRoom", seqNo: 122))) } // MARK: ---- retrieves recent messages if there was a last message and there has already been a poll this session it("retrieves recent messages if there was a last message and there has already been a poll this session") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 123 @@ -233,22 +229,22 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints[test: 2].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 2].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomMessagesSince("testRoom", seqNo: 123))) } @@ -257,9 +253,9 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ does not call the inbox and outbox endpoints it("does not call the inbox and outbox endpoints") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -267,38 +263,34 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).toNot(contain(.inbox)) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).toNot(contain(.outbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).toNot(contain(.inbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).toNot(contain(.outbox)) } } // MARK: ---- when blinded and checking for message requests context("when blinded and checking for message requests") { - beforeEach { - mockLibSessionCache.when { $0.get(.checkForCommunityMessageRequests) }.thenReturn(true) - } - // MARK: ------ includes the inbox and outbox endpoints it("includes the inbox and outbox endpoints") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -306,31 +298,31 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: true, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.inbox)) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.outbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.inbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.outbox)) } // MARK: ------ retrieves recent inbox messages if there was no last message it("retrieves recent inbox messages if there was no last message") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -338,30 +330,30 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: true, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.inbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.inbox)) } // MARK: ------ retrieves inbox messages since the last message if there was one it("retrieves inbox messages since the last message if there was one") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -369,30 +361,30 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 124, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: true, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.inboxSince(id: 124))) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.inboxSince(id: 124))) } // MARK: ------ retrieves recent outbox messages if there was no last message it("retrieves recent outbox messages if there was no last message") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -400,30 +392,30 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: true, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.outbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.outbox)) } // MARK: ------ retrieves outbox messages since the last message if there was one it("retrieves outbox messages since the last message if there was one") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -431,37 +423,33 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 125, + checkForCommunityMessageRequests: true, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).to(contain(.outboxSince(id: 125))) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).to(contain(.outboxSince(id: 125))) } } // MARK: ---- when blinded and not checking for message requests context("when blinded and not checking for message requests") { - beforeEach { - mockLibSessionCache.when { $0.get(.checkForCommunityMessageRequests) }.thenReturn(false) - } - // MARK: ------ includes the inbox and outbox endpoints it("does not include the inbox endpoint") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -469,30 +457,30 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).toNot(contain(.inbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).toNot(contain(.inbox)) } // MARK: ------ does not retrieve recent inbox messages if there was no last message it("does not retrieve recent inbox messages if there was no last message") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -500,30 +488,30 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 0, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).toNot(contain(.inbox)) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).toNot(contain(.inbox)) } // MARK: ------ does not retrieve inbox messages since the last message if there was one it("does not retrieve inbox messages since the last message if there was one") { expect { - preparedRequest = try OpenGroupAPI.preparedPoll( + preparedRequest = try Network.SOGS.preparedPoll( roomInfo: [ - OpenGroupAPI.RoomInfo( + Network.SOGS.PollRoomInfo( roomToken: "testRoom", infoUpdates: 0, sequenceNumber: 0 @@ -531,22 +519,22 @@ class OpenGroupAPISpec: QuickSpec { ], lastInboxMessageId: 124, lastOutboxMessageId: 0, + checkForCommunityMessageRequests: false, hasPerformedInitialPoll: true, timeSinceLastPoll: 0, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - expect(preparedRequest?.batchEndpoints as? [OpenGroupAPI.Endpoint]).toNot(contain(.inboxSince(id: 124))) + expect(preparedRequest?.batchEndpoints as? [Network.SOGS.Endpoint]).toNot(contain(.inboxSince(id: 124))) } } } @@ -555,16 +543,15 @@ class OpenGroupAPISpec: QuickSpec { context("when preparing a capabilities request") { // MARK: ---- generates the request correctly it("generates the request and handles the response correctly") { - var preparedRequest: Network.PreparedRequest? + var preparedRequest: Network.PreparedRequest? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilities( + preparedRequest = try Network.SOGS.preparedCapabilities( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -580,16 +567,15 @@ class OpenGroupAPISpec: QuickSpec { context("when preparing a rooms request") { // MARK: ---- generates the request correctly it("generates the request correctly") { - var preparedRequest: Network.PreparedRequest<[OpenGroupAPI.Room]>? + var preparedRequest: Network.PreparedRequest<[Network.SOGS.Room]>? expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -603,20 +589,19 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing a capabilitiesAndRoom request context("when preparing a capabilitiesAndRoom request") { - @TestState var preparedRequest: Network.PreparedRequest? + @TestState var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRoom( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRoom( roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -624,9 +609,9 @@ class OpenGroupAPISpec: QuickSpec { }.toNot(throwError()) expect(preparedRequest?.batchEndpoints.count).to(equal(2)) - expect(preparedRequest?.batchEndpoints[test: 0].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(Network.SOGS.Endpoint.self)) .to(equal(.capabilities)) - expect(preparedRequest?.batchEndpoints[test: 1].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(Network.SOGS.Endpoint.self)) .to(equal(.room("testRoom"))) expect(preparedRequest?.path).to(equal("/sequence")) @@ -639,18 +624,17 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn(Network.BatchResponse.mockCapabilitiesAndRoomResponse) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRoom( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRoom( roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -675,18 +659,17 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn(Network.BatchResponse.mockCapabilitiesAndBanResponse) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRoom( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRoom( roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -709,18 +692,17 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn(Network.BatchResponse.mockBanAndRoomResponse) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRoom( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRoom( roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -740,22 +722,21 @@ class OpenGroupAPISpec: QuickSpec { } } - describe("an OpenGroupAPI") { + describe("an Network.SOGS") { // MARK: -- when preparing a capabilitiesAndRooms request context("when preparing a capabilitiesAndRooms request") { - @TestState var preparedRequest: Network.PreparedRequest? + @TestState var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRooms( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -763,13 +744,50 @@ class OpenGroupAPISpec: QuickSpec { }.toNot(throwError()) expect(preparedRequest?.batchEndpoints.count).to(equal(2)) - expect(preparedRequest?.batchEndpoints[test: 0].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(Network.SOGS.Endpoint.self)) .to(equal(.capabilities)) - expect(preparedRequest?.batchEndpoints[test: 1].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(Network.SOGS.Endpoint.self)) .to(equal(.rooms)) expect(preparedRequest?.path).to(equal("/sequence")) expect(preparedRequest?.method.rawValue).to(equal("POST")) + + expect(preparedRequest?.headers).toNot(beEmpty()) + expect(preparedRequest?.headers).to(equal([ + HTTPHeader.sogsNonce: "pK6YRtQApl4NhECGizF0Cg==", + HTTPHeader.sogsTimestamp: "1234567890", + HTTPHeader.sogsSignature: "VGVzdFNvZ3NTaWduYXR1cmU=", + HTTPHeader.sogsPubKey: "1588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b" + ])) + } + + // MARK: ---- generates the request correctly and skips adding request headers + it("generates the request correctly and skips adding request headers") { + expect { + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRooms( + authMethod: Authentication.community( + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, + forceBlinded: false + ), + skipAuthentication: true, + using: dependencies + ) + }.toNot(throwError()) + + expect(preparedRequest?.batchEndpoints.count).to(equal(2)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(Network.SOGS.Endpoint.self)) + .to(equal(.capabilities)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(Network.SOGS.Endpoint.self)) + .to(equal(.rooms)) + + expect(preparedRequest?.path).to(equal("/sequence")) + expect(preparedRequest?.method.rawValue).to(equal("POST")) + + expect(preparedRequest?.headers).to(beEmpty()) } // MARK: ---- processes a valid response correctly @@ -778,17 +796,16 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn(Network.BatchResponse.mockCapabilitiesAndRoomsResponse) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomsResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomsResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRooms( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -813,25 +830,24 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn( MockNetwork.batchResponseData(with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), ( - OpenGroupAPI.Endpoint.userBan(""), - OpenGroupAPI.DirectMessage.mockBatchSubResponse() + Network.SOGS.Endpoint.userBan(""), + Network.SOGS.DirectMessage.mockBatchSubResponse() ) ]) ) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomsResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomsResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRooms( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -854,17 +870,16 @@ class OpenGroupAPISpec: QuickSpec { .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } .thenReturn(Network.BatchResponse.mockBanAndRoomsResponse) - var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomsResponse)? + var response: (info: ResponseInfoType, data: Network.SOGS.CapabilitiesAndRoomsResponse)? expect { - preparedRequest = try OpenGroupAPI.preparedCapabilitiesAndRooms( + preparedRequest = try Network.SOGS.preparedCapabilitiesAndRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -885,24 +900,23 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing a send message request context("when preparing a send message request") { - @TestState var preparedRequest: Network.PreparedRequest? + @TestState var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -918,27 +932,26 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs the message correctly it("signs the message correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.SendMessageRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.SendMessageRequest.self, using: dependencies) + let requestBody: Network.SOGS.SendSOGSMessageRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.SendSOGSMessageRequest.self, using: dependencies) expect(requestBody?.data).to(equal("test".data(using: .utf8))) expect(requestBody?.signature).to(equal("TestStandardSignature".data(using: .utf8))) } @@ -948,24 +961,23 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519SecretKey }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -975,24 +987,23 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519Seed }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1004,24 +1015,23 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1032,27 +1042,26 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs the message correctly it("signs the message correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.SendMessageRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.SendMessageRequest.self, using: dependencies) + let requestBody: Network.SOGS.SendSOGSMessageRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.SendSOGSMessageRequest.self, using: dependencies) expect(requestBody?.data).to(equal("test".data(using: .utf8))) expect(requestBody?.signature).to(equal("TestSogsSignature".data(using: .utf8))) } @@ -1062,24 +1071,23 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519SecretKey }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1089,24 +1097,23 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519Seed }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1118,24 +1125,23 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( plaintext: "test".data(using: .utf8)!, roomToken: "testRoom", whisperTo: nil, whisperMods: false, fileIds: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1144,21 +1150,20 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing an individual message request context("when preparing an individual message request") { - var preparedRequest: Network.PreparedRequest? + var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessage( + preparedRequest = try Network.SOGS.preparedMessage( id: 123, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1177,18 +1182,17 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1204,26 +1208,25 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs the message correctly it("signs the message correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UpdateMessageRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UpdateMessageRequest.self, using: dependencies) + let requestBody: Network.SOGS.UpdateMessageRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UpdateMessageRequest.self, using: dependencies) expect(requestBody?.data).to(equal("test".data(using: .utf8))) expect(requestBody?.signature).to(equal("TestStandardSignature".data(using: .utf8))) } @@ -1233,23 +1236,22 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519SecretKey }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1259,23 +1261,22 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519Seed }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1287,23 +1288,22 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1314,26 +1314,25 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs the message correctly it("signs the message correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UpdateMessageRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UpdateMessageRequest.self, using: dependencies) + let requestBody: Network.SOGS.UpdateMessageRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UpdateMessageRequest.self, using: dependencies) expect(requestBody?.data).to(equal("test".data(using: .utf8))) expect(requestBody?.signature).to(equal("TestSogsSignature".data(using: .utf8))) } @@ -1343,23 +1342,22 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519SecretKey }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1369,23 +1367,22 @@ class OpenGroupAPISpec: QuickSpec { mockGeneralCache.when { $0.ed25519Seed }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1397,23 +1394,22 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedMessageUpdate( + preparedRequest = try Network.SOGS.preparedMessageUpdate( id: 123, plaintext: "test".data(using: .utf8)!, fileIds: nil, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -1427,16 +1423,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessageDelete( + preparedRequest = try Network.SOGS.preparedMessageDelete( id: 123, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1455,16 +1450,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedMessagesDeleteAll( + preparedRequest = try Network.SOGS.preparedMessagesDeleteAll( sessionId: "testUserId", roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1483,16 +1477,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedPinMessage( + preparedRequest = try Network.SOGS.preparedPinMessage( id: 123, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1511,16 +1504,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUnpinMessage( + preparedRequest = try Network.SOGS.preparedUnpinMessage( id: 123, roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1539,15 +1531,14 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUnpinAll( + preparedRequest = try Network.SOGS.preparedUnpinAll( roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1566,16 +1557,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUpload( + preparedRequest = try Network.SOGS.preparedUpload( data: Data([1, 2, 3]), roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1593,23 +1583,22 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the download url string correctly it("generates the download url string correctly") { - expect(OpenGroupAPI.downloadUrlString(for: "1", server: "testserver", roomToken: "roomToken")) + expect(Network.SOGS.downloadUrlString(for: "1", server: "testserver", roomToken: "roomToken")) .to(equal("testserver/room/roomToken/file/1")) } // MARK: ---- generates the download destination correctly when given an id it("generates the download destination correctly when given an id") { expect { - preparedRequest = try OpenGroupAPI.preparedDownload( + preparedRequest = try Network.SOGS.preparedDownload( fileId: "1", roomToken: "roomToken", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1626,19 +1615,42 @@ class OpenGroupAPISpec: QuickSpec { ])) } + // MARK: ---- generates the download destination correctly when given an id and skips adding request headers + it("generates the download destination correctly when given an id and skips adding request headers") { + expect { + preparedRequest = try Network.SOGS.preparedDownload( + fileId: "1", + roomToken: "roomToken", + authMethod: Authentication.community( + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, + forceBlinded: false + ), + skipAuthentication: true, + using: dependencies + ) + }.toNot(throwError()) + + expect(preparedRequest?.path).to(equal("/room/roomToken/file/1")) + expect(preparedRequest?.method.rawValue).to(equal("GET")) + expect(preparedRequest?.headers).to(beEmpty()) + } + // MARK: ---- generates the download request correctly when given a URL it("generates the download request correctly when given a URL") { expect { - preparedRequest = try OpenGroupAPI.preparedDownload( + preparedRequest = try Network.SOGS.preparedDownload( url: URL(string: "http://oxen.io/room/roomToken/file/1")!, roomToken: "roomToken", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1658,19 +1670,18 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing an inbox request context("when preparing an inbox request") { - @TestState var preparedRequest: Network.PreparedRequest<[OpenGroupAPI.DirectMessage]?>? + @TestState var preparedRequest: Network.PreparedRequest<[Network.SOGS.DirectMessage]?>? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedInbox( + preparedRequest = try Network.SOGS.preparedInbox( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1684,20 +1695,19 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing an inbox since request context("when preparing an inbox since request") { - @TestState var preparedRequest: Network.PreparedRequest<[OpenGroupAPI.DirectMessage]?>? + @TestState var preparedRequest: Network.PreparedRequest<[Network.SOGS.DirectMessage]?>? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedInboxSince( + preparedRequest = try Network.SOGS.preparedInboxSince( id: 1, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1711,19 +1721,18 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing a clear inbox request context("when preparing an inbox since request") { - @TestState var preparedRequest: Network.PreparedRequest? + @TestState var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedClearInbox( + preparedRequest = try Network.SOGS.preparedClearInbox( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1737,21 +1746,20 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing a send direct message request context("when preparing a send direct message request") { - @TestState var preparedRequest: Network.PreparedRequest? + @TestState var preparedRequest: Network.PreparedRequest? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedSend( + preparedRequest = try Network.SOGS.preparedSend( ciphertext: "test".data(using: .utf8)!, toInboxFor: "testUserId", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1764,7 +1772,7 @@ class OpenGroupAPISpec: QuickSpec { } } - describe("an OpenGroupAPI") { + describe("an Network.SOGS") { // MARK: -- when preparing a ban user request context("when preparing a ban user request") { @TestState var preparedRequest: Network.PreparedRequest? @@ -1772,17 +1780,16 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUserBan( + preparedRequest = try Network.SOGS.preparedUserBan( sessionId: "testUserId", for: nil, from: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1796,25 +1803,24 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does a global ban if no room tokens are provided it("does a global ban if no room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserBan( + preparedRequest = try Network.SOGS.preparedUserBan( sessionId: "testUserId", for: nil, from: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserBanRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserBanRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserBanRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserBanRequest.self, using: dependencies) expect(requestBody?.global).to(beTrue()) expect(requestBody?.rooms).to(beNil()) } @@ -1822,25 +1828,24 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does room specific bans if room tokens are provided it("does room specific bans if room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserBan( + preparedRequest = try Network.SOGS.preparedUserBan( sessionId: "testUserId", for: nil, from: ["testRoom"], authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserBanRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserBanRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserBanRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserBanRequest.self, using: dependencies) expect(requestBody?.global).to(beNil()) expect(requestBody?.rooms).to(equal(["testRoom"])) } @@ -1853,16 +1858,15 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUserUnban( + preparedRequest = try Network.SOGS.preparedUserUnban( sessionId: "testUserId", from: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1876,24 +1880,23 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does a global unban if no room tokens are provided it("does a global unban if no room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserUnban( + preparedRequest = try Network.SOGS.preparedUserUnban( sessionId: "testUserId", from: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserUnbanRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserUnbanRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserUnbanRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserUnbanRequest.self, using: dependencies) expect(requestBody?.global).to(beTrue()) expect(requestBody?.rooms).to(beNil()) } @@ -1901,24 +1904,23 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does room specific unbans if room tokens are provided it("does room specific unbans if room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserUnban( + preparedRequest = try Network.SOGS.preparedUserUnban( sessionId: "testUserId", from: ["testRoom"], authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserUnbanRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserUnbanRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserUnbanRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserUnbanRequest.self, using: dependencies) expect(requestBody?.global).to(beNil()) expect(requestBody?.rooms).to(equal(["testRoom"])) } @@ -1931,19 +1933,18 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUserModeratorUpdate( + preparedRequest = try Network.SOGS.preparedUserModeratorUpdate( sessionId: "testUserId", moderator: true, admin: nil, visible: true, for: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -1957,27 +1958,26 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does a global update if no room tokens are provided it("does a global update if no room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserModeratorUpdate( + preparedRequest = try Network.SOGS.preparedUserModeratorUpdate( sessionId: "testUserId", moderator: true, admin: nil, visible: true, for: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserModeratorRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserModeratorRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserModeratorRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserModeratorRequest.self, using: dependencies) expect(requestBody?.global).to(beTrue()) expect(requestBody?.rooms).to(beNil()) } @@ -1985,27 +1985,26 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- does room specific updates if room tokens are provided it("does room specific updates if room tokens are provided") { expect { - preparedRequest = try OpenGroupAPI.preparedUserModeratorUpdate( + preparedRequest = try Network.SOGS.preparedUserModeratorUpdate( sessionId: "testUserId", moderator: true, admin: nil, visible: true, for: ["testRoom"], authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) }.toNot(throwError()) - let requestBody: OpenGroupAPI.UserModeratorRequest? = try? preparedRequest?.body? - .decoded(as: OpenGroupAPI.UserModeratorRequest.self, using: dependencies) + let requestBody: Network.SOGS.UserModeratorRequest? = try? preparedRequest?.body? + .decoded(as: Network.SOGS.UserModeratorRequest.self, using: dependencies) expect(requestBody?.global).to(beNil()) expect(requestBody?.rooms).to(equal(["testRoom"])) } @@ -2013,19 +2012,18 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ---- fails if neither moderator or admin are set it("fails if neither moderator or admin are set") { expect { - preparedRequest = try OpenGroupAPI.preparedUserModeratorUpdate( + preparedRequest = try Network.SOGS.preparedUserModeratorUpdate( sessionId: "testUserId", moderator: nil, admin: nil, visible: true, for: nil, authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -2038,21 +2036,20 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when preparing a ban and delete all request context("when preparing a ban and delete all request") { - @TestState var preparedRequest: Network.PreparedRequest>? + @TestState var preparedRequest: Network.PreparedRequest>? // MARK: ---- generates the request correctly it("generates the request correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedUserBanAndDeleteAllMessages( + preparedRequest = try Network.SOGS.preparedUserBanAndDeleteAllMessages( sessionId: "testUserId", roomToken: "testRoom", authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -2062,37 +2059,36 @@ class OpenGroupAPISpec: QuickSpec { expect(preparedRequest?.path).to(equal("/sequence")) expect(preparedRequest?.method.rawValue).to(equal("POST")) expect(preparedRequest?.batchEndpoints.count).to(equal(2)) - expect(preparedRequest?.batchEndpoints[test: 0].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 0].asType(Network.SOGS.Endpoint.self)) .to(equal(.userBan("testUserId"))) - expect(preparedRequest?.batchEndpoints[test: 1].asType(OpenGroupAPI.Endpoint.self)) + expect(preparedRequest?.batchEndpoints[test: 1].asType(Network.SOGS.Endpoint.self)) .to(equal(.roomDeleteMessages("testRoom", sessionId: "testUserId"))) } } } - describe("an OpenGroupAPI") { + describe("an Network.SOGS") { // MARK: -- when signing context("when signing") { - @TestState var preparedRequest: Network.PreparedRequest<[OpenGroupAPI.Room]>? + @TestState var preparedRequest: Network.PreparedRequest<[Network.SOGS.Room]>? // MARK: ---- fails when there is no ed25519SecretKey it("fails when there is no ed25519SecretKey") { mockGeneralCache.when { $0.ed25519SecretKey }.thenReturn([]) expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -2102,14 +2098,13 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs correctly it("signs correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies @@ -2138,19 +2133,18 @@ class OpenGroupAPISpec: QuickSpec { .thenThrow(CryptoError.failedToGenerateOutput) expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: false, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -2161,14 +2155,13 @@ class OpenGroupAPISpec: QuickSpec { // MARK: ------ signs correctly it("signs correctly") { expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies @@ -2197,19 +2190,18 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -2221,19 +2213,18 @@ class OpenGroupAPISpec: QuickSpec { .thenReturn(nil) expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [.sogs, .blind] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: true, + supportsBlinding: true, forceBlinded: false ), using: dependencies ) - }.to(throwError(OpenGroupAPIError.signingFailed)) + }.to(throwError(SOGSError.signingFailed)) expect(preparedRequest).to(beNil()) } @@ -2242,29 +2233,59 @@ class OpenGroupAPISpec: QuickSpec { // MARK: -- when sending context("when sending") { - @TestState var preparedRequest: Network.PreparedRequest<[OpenGroupAPI.Room]>? + @TestState var preparedRequest: Network.PreparedRequest<[Network.SOGS.Room]>? beforeEach { mockNetwork .when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) } - .thenReturn(MockNetwork.response(type: [OpenGroupAPI.Room].self)) + .thenReturn(MockNetwork.response(type: [Network.SOGS.Room].self)) } // MARK: ---- triggers sending correctly it("triggers sending correctly") { - var response: (info: ResponseInfoType, data: [OpenGroupAPI.Room])? + var response: (info: ResponseInfoType, data: [Network.SOGS.Room])? expect { - preparedRequest = try OpenGroupAPI.preparedRooms( + preparedRequest = try Network.SOGS.preparedRooms( authMethod: Authentication.community( - info: LibSession.OpenGroupCapabilityInfo( - roomToken: "", - server: "testserver", - publicKey: TestConstants.publicKey, - capabilities: [] - ), + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, + forceBlinded: false + ), + using: dependencies + ) + }.toNot(throwError()) + + preparedRequest? + .send(using: dependencies) + .handleEvents(receiveOutput: { result in response = result }) + .mapError { error.setting(to: $0) } + .sinkAndStore(in: &disposables) + + expect(preparedRequest?.headers).toNot(beEmpty()) + + expect(response).toNot(beNil()) + expect(error).to(beNil()) + } + + // MARK: ---- triggers sending correctly without headers + it("triggers sending correctly without headers") { + var response: (info: ResponseInfoType, data: [Network.SOGS.Room])? + + expect { + preparedRequest = try Network.SOGS.preparedRooms( + authMethod: Authentication.community( + roomToken: "", + server: "testserver", + publicKey: TestConstants.publicKey, + hasCapabilities: false, + supportsBlinding: false, forceBlinded: false ), + skipAuthentication: true, using: dependencies ) }.toNot(throwError()) @@ -2274,6 +2295,8 @@ class OpenGroupAPISpec: QuickSpec { .handleEvents(receiveOutput: { result in response = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) + + expect(preparedRequest?.headers).to(beEmpty()) expect(response).toNot(beNil()) expect(error).to(beNil()) @@ -2290,15 +2313,15 @@ extension Network.BatchResponse { static let mockCapabilitiesAndRoomResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.room("testRoom"), OpenGroupAPI.Room.mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.room("testRoom"), Network.SOGS.Room.mockBatchSubResponse()) ] ) static let mockCapabilitiesAndRoomsResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.rooms, [OpenGroupAPI.Room].mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.rooms, [Network.SOGS.Room].mockBatchSubResponse()) ] ) @@ -2306,22 +2329,22 @@ extension Network.BatchResponse { static let mockCapabilitiesAndBanResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()) + (Network.SOGS.Endpoint.capabilities, Network.SOGS.CapabilitiesResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()) ] ) static let mockBanAndRoomResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.room("testRoom"), OpenGroupAPI.Room.mockBatchSubResponse()) + (Network.SOGS.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.room("testRoom"), Network.SOGS.Room.mockBatchSubResponse()) ] ) static let mockBanAndRoomsResponse: AnyPublisher<(ResponseInfoType, Data?), Error> = MockNetwork.batchResponseData( with: [ - (OpenGroupAPI.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), - (OpenGroupAPI.Endpoint.rooms, [OpenGroupAPI.Room].mockBatchSubResponse()) + (Network.SOGS.Endpoint.userBan(""), NoResponse.mockBatchSubResponse()), + (Network.SOGS.Endpoint.rooms, [Network.SOGS.Room].mockBatchSubResponse()) ] ) } diff --git a/SessionMessagingKitTests/Open Groups/Types/PersonalizationSpec.swift b/SessionNetworkingKitTests/SOGS/Types/PersonalizationSpec.swift similarity index 78% rename from SessionMessagingKitTests/Open Groups/Types/PersonalizationSpec.swift rename to SessionNetworkingKitTests/SOGS/Types/PersonalizationSpec.swift index ab6c201a14..5b24ce72de 100644 --- a/SessionMessagingKitTests/Open Groups/Types/PersonalizationSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Types/PersonalizationSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class PersonalizationSpec: QuickSpec { override class func spec() { @@ -13,9 +13,9 @@ class PersonalizationSpec: QuickSpec { describe("a Personalization") { // MARK: -- generates bytes correctly it("generates bytes correctly") { - expect(OpenGroupAPI.Personalization.sharedKeys.bytes) + expect(Network.SOGS.Personalization.sharedKeys.bytes) .to(equal([115, 111, 103, 115, 46, 115, 104, 97, 114, 101, 100, 95, 107, 101, 121, 115])) - expect(OpenGroupAPI.Personalization.authHeader.bytes) + expect(Network.SOGS.Personalization.authHeader.bytes) .to(equal([115, 111, 103, 115, 46, 97, 117, 116, 104, 95, 104, 101, 97, 100, 101, 114])) } } diff --git a/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift b/SessionNetworkingKitTests/SOGS/Types/SOGSEndpointSpec.swift similarity index 56% rename from SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift rename to SessionNetworkingKitTests/SOGS/Types/SOGSEndpointSpec.swift index 88e568f21b..33b0929de7 100644 --- a/SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift +++ b/SessionNetworkingKitTests/SOGS/Types/SOGSEndpointSpec.swift @@ -1,13 +1,11 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import SessionSnodeKit -import SessionUtilitiesKit import Quick import Nimble -@testable import SessionMessagingKit +@testable import SessionNetworkingKit class SOGSEndpointSpec: QuickSpec { override class func spec() { @@ -15,12 +13,12 @@ class SOGSEndpointSpec: QuickSpec { describe("a SOGSEndpoint") { // MARK: -- provides the correct batch request variant it("provides the correct batch request variant") { - expect(OpenGroupAPI.Endpoint.batchRequestVariant).to(equal(.sogs)) + expect(Network.SOGS.Endpoint.batchRequestVariant).to(equal(.sogs)) } // MARK: -- excludes the correct headers from batch sub request it("excludes the correct headers from batch sub request") { - expect(OpenGroupAPI.Endpoint.excludedSubRequestHeaders).to(equal([ + expect(Network.SOGS.Endpoint.excludedSubRequestHeaders).to(equal([ HTTPHeader.sogsPubKey, HTTPHeader.sogsTimestamp, HTTPHeader.sogsNonce, @@ -32,53 +30,53 @@ class SOGSEndpointSpec: QuickSpec { it("generates the path value correctly") { // Utility - expect(OpenGroupAPI.Endpoint.onion.path).to(equal("oxen/v4/lsrpc")) - expect(OpenGroupAPI.Endpoint.batch.path).to(equal("batch")) - expect(OpenGroupAPI.Endpoint.sequence.path).to(equal("sequence")) - expect(OpenGroupAPI.Endpoint.capabilities.path).to(equal("capabilities")) + expect(Network.SOGS.Endpoint.onion.path).to(equal("oxen/v4/lsrpc")) + expect(Network.SOGS.Endpoint.batch.path).to(equal("batch")) + expect(Network.SOGS.Endpoint.sequence.path).to(equal("sequence")) + expect(Network.SOGS.Endpoint.capabilities.path).to(equal("capabilities")) // Rooms - expect(OpenGroupAPI.Endpoint.rooms.path).to(equal("rooms")) - expect(OpenGroupAPI.Endpoint.room("test").path).to(equal("room/test")) - expect(OpenGroupAPI.Endpoint.roomPollInfo("test", 123).path).to(equal("room/test/pollInfo/123")) + expect(Network.SOGS.Endpoint.rooms.path).to(equal("rooms")) + expect(Network.SOGS.Endpoint.room("test").path).to(equal("room/test")) + expect(Network.SOGS.Endpoint.roomPollInfo("test", 123).path).to(equal("room/test/pollInfo/123")) // Messages - expect(OpenGroupAPI.Endpoint.roomMessage("test").path).to(equal("room/test/message")) - expect(OpenGroupAPI.Endpoint.roomMessageIndividual("test", id: 123).path).to(equal("room/test/message/123")) - expect(OpenGroupAPI.Endpoint.roomMessagesRecent("test").path).to(equal("room/test/messages/recent")) - expect(OpenGroupAPI.Endpoint.roomMessagesBefore("test", id: 123).path).to(equal("room/test/messages/before/123")) - expect(OpenGroupAPI.Endpoint.roomMessagesSince("test", seqNo: 123).path) + expect(Network.SOGS.Endpoint.roomMessage("test").path).to(equal("room/test/message")) + expect(Network.SOGS.Endpoint.roomMessageIndividual("test", id: 123).path).to(equal("room/test/message/123")) + expect(Network.SOGS.Endpoint.roomMessagesRecent("test").path).to(equal("room/test/messages/recent")) + expect(Network.SOGS.Endpoint.roomMessagesBefore("test", id: 123).path).to(equal("room/test/messages/before/123")) + expect(Network.SOGS.Endpoint.roomMessagesSince("test", seqNo: 123).path) .to(equal("room/test/messages/since/123")) - expect(OpenGroupAPI.Endpoint.roomDeleteMessages("test", sessionId: "testId").path) + expect(Network.SOGS.Endpoint.roomDeleteMessages("test", sessionId: "testId").path) .to(equal("room/test/all/testId")) // Pinning - expect(OpenGroupAPI.Endpoint.roomPinMessage("test", id: 123).path).to(equal("room/test/pin/123")) - expect(OpenGroupAPI.Endpoint.roomUnpinMessage("test", id: 123).path).to(equal("room/test/unpin/123")) - expect(OpenGroupAPI.Endpoint.roomUnpinAll("test").path).to(equal("room/test/unpin/all")) + expect(Network.SOGS.Endpoint.roomPinMessage("test", id: 123).path).to(equal("room/test/pin/123")) + expect(Network.SOGS.Endpoint.roomUnpinMessage("test", id: 123).path).to(equal("room/test/unpin/123")) + expect(Network.SOGS.Endpoint.roomUnpinAll("test").path).to(equal("room/test/unpin/all")) // Files - expect(OpenGroupAPI.Endpoint.roomFile("test").path).to(equal("room/test/file")) - expect(OpenGroupAPI.Endpoint.roomFileIndividual("test", "123").path).to(equal("room/test/file/123")) + expect(Network.SOGS.Endpoint.roomFile("test").path).to(equal("room/test/file")) + expect(Network.SOGS.Endpoint.roomFileIndividual("test", "123").path).to(equal("room/test/file/123")) // Inbox/Outbox (Message Requests) - expect(OpenGroupAPI.Endpoint.inbox.path).to(equal("inbox")) - expect(OpenGroupAPI.Endpoint.inboxSince(id: 123).path).to(equal("inbox/since/123")) - expect(OpenGroupAPI.Endpoint.inboxFor(sessionId: "test").path).to(equal("inbox/test")) + expect(Network.SOGS.Endpoint.inbox.path).to(equal("inbox")) + expect(Network.SOGS.Endpoint.inboxSince(id: 123).path).to(equal("inbox/since/123")) + expect(Network.SOGS.Endpoint.inboxFor(sessionId: "test").path).to(equal("inbox/test")) - expect(OpenGroupAPI.Endpoint.outbox.path).to(equal("outbox")) - expect(OpenGroupAPI.Endpoint.outboxSince(id: 123).path).to(equal("outbox/since/123")) + expect(Network.SOGS.Endpoint.outbox.path).to(equal("outbox")) + expect(Network.SOGS.Endpoint.outboxSince(id: 123).path).to(equal("outbox/since/123")) // Users - expect(OpenGroupAPI.Endpoint.userBan("test").path).to(equal("user/test/ban")) - expect(OpenGroupAPI.Endpoint.userUnban("test").path).to(equal("user/test/unban")) - expect(OpenGroupAPI.Endpoint.userModerator("test").path).to(equal("user/test/moderator")) + expect(Network.SOGS.Endpoint.userBan("test").path).to(equal("user/test/ban")) + expect(Network.SOGS.Endpoint.userUnban("test").path).to(equal("user/test/unban")) + expect(Network.SOGS.Endpoint.userModerator("test").path).to(equal("user/test/moderator")) } } } diff --git a/SessionNetworkingKitTests/SOGS/Types/SOGSErrorSpec.swift b/SessionNetworkingKitTests/SOGS/Types/SOGSErrorSpec.swift new file mode 100644 index 0000000000..e994dea0b3 --- /dev/null +++ b/SessionNetworkingKitTests/SOGS/Types/SOGSErrorSpec.swift @@ -0,0 +1,24 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +import Quick +import Nimble + +@testable import SessionNetworkingKit + +class SOGSErrorSpec: QuickSpec { + override class func spec() { + // MARK: - a SOGSError + describe("a SOGSError") { + // MARK: -- generates the error description correctly + it("generates the error description correctly") { + expect(SOGSError.decryptionFailed.description).to(equal("Couldn't decrypt response.")) + expect(SOGSError.signingFailed.description).to(equal("Couldn't sign message.")) + expect(SOGSError.noPublicKey.description).to(equal("Couldn't find server public key.")) + expect(SOGSError.invalidEmoji.description).to(equal("The emoji is invalid.")) + expect(SOGSError.invalidPoll.description).to(equal("Poller in invalid state.")) + } + } + } +} diff --git a/SessionSnodeKitTests/SessionSnodeKit.xctestplan b/SessionNetworkingKitTests/SessionNetworkingKit.xctestplan similarity index 90% rename from SessionSnodeKitTests/SessionSnodeKit.xctestplan rename to SessionNetworkingKitTests/SessionNetworkingKit.xctestplan index ea699b6efd..52ef80ee0c 100644 --- a/SessionSnodeKitTests/SessionSnodeKit.xctestplan +++ b/SessionNetworkingKitTests/SessionNetworkingKit.xctestplan @@ -17,7 +17,7 @@ "target" : { "containerPath" : "container:Session.xcodeproj", "identifier" : "FDB5DAF92A981C42002C8721", - "name" : "SessionSnodeKitTests" + "name" : "SessionNetworkingKitTests" } } ], diff --git a/SessionSnodeKitTests/Types/BatchRequestSpec.swift b/SessionNetworkingKitTests/Types/BatchRequestSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/BatchRequestSpec.swift rename to SessionNetworkingKitTests/Types/BatchRequestSpec.swift index 36b33bd18a..05554a2be1 100644 --- a/SessionSnodeKitTests/Types/BatchRequestSpec.swift +++ b/SessionNetworkingKitTests/Types/BatchRequestSpec.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class BatchRequestSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/BatchResponseSpec.swift b/SessionNetworkingKitTests/Types/BatchResponseSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/BatchResponseSpec.swift rename to SessionNetworkingKitTests/Types/BatchResponseSpec.swift index 3fe9ea5575..eac736ac8b 100644 --- a/SessionSnodeKitTests/Types/BatchResponseSpec.swift +++ b/SessionNetworkingKitTests/Types/BatchResponseSpec.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class BatchResponseSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/BencodeResponseSpec.swift b/SessionNetworkingKitTests/Types/BencodeResponseSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/BencodeResponseSpec.swift rename to SessionNetworkingKitTests/Types/BencodeResponseSpec.swift index e0e6add5f9..0bd213f4ec 100644 --- a/SessionSnodeKitTests/Types/BencodeResponseSpec.swift +++ b/SessionNetworkingKitTests/Types/BencodeResponseSpec.swift @@ -6,7 +6,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class BencodeResponseSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/DestinationSpec.swift b/SessionNetworkingKitTests/Types/DestinationSpec.swift similarity index 98% rename from SessionSnodeKitTests/Types/DestinationSpec.swift rename to SessionNetworkingKitTests/Types/DestinationSpec.swift index 6e17460ac5..e226a25f21 100644 --- a/SessionSnodeKitTests/Types/DestinationSpec.swift +++ b/SessionNetworkingKitTests/Types/DestinationSpec.swift @@ -5,7 +5,7 @@ import Foundation import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class DestinationSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/HeaderSpec.swift b/SessionNetworkingKitTests/Types/HeaderSpec.swift similarity index 93% rename from SessionSnodeKitTests/Types/HeaderSpec.swift rename to SessionNetworkingKitTests/Types/HeaderSpec.swift index ef25b0c5a7..9f73a41944 100644 --- a/SessionSnodeKitTests/Types/HeaderSpec.swift +++ b/SessionNetworkingKitTests/Types/HeaderSpec.swift @@ -6,7 +6,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class HeaderSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/PreparedRequestSendingSpec.swift b/SessionNetworkingKitTests/Types/PreparedRequestSendingSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/PreparedRequestSendingSpec.swift rename to SessionNetworkingKitTests/Types/PreparedRequestSendingSpec.swift index a75966aa6b..a1ad6f438b 100644 --- a/SessionSnodeKitTests/Types/PreparedRequestSendingSpec.swift +++ b/SessionNetworkingKitTests/Types/PreparedRequestSendingSpec.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class PreparedRequestSendingSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/PreparedRequestSpec.swift b/SessionNetworkingKitTests/Types/PreparedRequestSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/PreparedRequestSpec.swift rename to SessionNetworkingKitTests/Types/PreparedRequestSpec.swift index 7ba2a676ac..7b949b5fde 100644 --- a/SessionSnodeKitTests/Types/PreparedRequestSpec.swift +++ b/SessionNetworkingKitTests/Types/PreparedRequestSpec.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class PreparedRequestSpec: QuickSpec { override class func spec() { diff --git a/SessionSnodeKitTests/Types/RequestSpec.swift b/SessionNetworkingKitTests/Types/RequestSpec.swift similarity index 99% rename from SessionSnodeKitTests/Types/RequestSpec.swift rename to SessionNetworkingKitTests/Types/RequestSpec.swift index 9888d30083..0b951cf1d3 100644 --- a/SessionSnodeKitTests/Types/RequestSpec.swift +++ b/SessionNetworkingKitTests/Types/RequestSpec.swift @@ -6,7 +6,7 @@ import SessionUtilitiesKit import Quick import Nimble -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class RequestSpec: QuickSpec { override class func spec() { diff --git a/SessionNetworkingKitTests/_TestUtilities/CommonSSKMockExtensions.swift b/SessionNetworkingKitTests/_TestUtilities/CommonSSKMockExtensions.swift new file mode 100644 index 0000000000..bbd0834908 --- /dev/null +++ b/SessionNetworkingKitTests/_TestUtilities/CommonSSKMockExtensions.swift @@ -0,0 +1,132 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +@testable import SessionNetworkingKit + +extension NoResponse: Mocked { + static var mock: NoResponse = NoResponse() +} + +extension Network.BatchSubResponse: MockedGeneric where T: Mocked { + typealias Generic = T + + static func mock(type: T.Type) -> Network.BatchSubResponse { + return Network.BatchSubResponse( + code: 200, + headers: [:], + body: Generic.mock, + failedToParseBody: false + ) + } +} + +extension Network.BatchSubResponse { + static func mockArrayValue(type: M.Type) -> Network.BatchSubResponse> { + return Network.BatchSubResponse( + code: 200, + headers: [:], + body: [M.mock], + failedToParseBody: false + ) + } +} + +extension Network.Destination: Mocked { + static var mock: Network.Destination = try! Network.Destination.server( + server: "testServer", + headers: [:], + x25519PublicKey: "" + ).withGeneratedUrl(for: MockEndpoint.mock) +} + +extension Network.SOGS.CapabilitiesResponse: Mocked { + static var mock: Network.SOGS.CapabilitiesResponse = Network.SOGS.CapabilitiesResponse(capabilities: [], missing: nil) +} + +extension Network.SOGS.Room: Mocked { + static var mock: Network.SOGS.Room = Network.SOGS.Room( + token: "test", + name: "testRoom", + roomDescription: nil, + infoUpdates: 1, + messageSequence: 1, + created: 1, + activeUsers: 1, + activeUsersCutoff: 1, + imageId: nil, + pinnedMessages: nil, + admin: false, + globalAdmin: false, + admins: [], + hiddenAdmins: nil, + moderator: false, + globalModerator: false, + moderators: [], + hiddenModerators: nil, + read: true, + defaultRead: nil, + defaultAccessible: nil, + write: true, + defaultWrite: nil, + upload: true, + defaultUpload: nil + ) +} + +extension Network.SOGS.RoomPollInfo: Mocked { + static var mock: Network.SOGS.RoomPollInfo = Network.SOGS.RoomPollInfo( + token: "test", + activeUsers: 1, + admin: false, + globalAdmin: false, + moderator: false, + globalModerator: false, + read: true, + defaultRead: nil, + defaultAccessible: nil, + write: true, + defaultWrite: nil, + upload: true, + defaultUpload: false, + details: .mock + ) +} + +extension Network.SOGS.Message: Mocked { + static var mock: Network.SOGS.Message = Network.SOGS.Message( + id: 100, + sender: TestConstants.blind15PublicKey, + posted: 1, + edited: nil, + deleted: nil, + seqNo: 1, + whisper: false, + whisperMods: false, + whisperTo: nil, + base64EncodedData: nil, + base64EncodedSignature: nil, + reactions: nil + ) +} + +extension Network.SOGS.SendDirectMessageResponse: Mocked { + static var mock: Network.SOGS.SendDirectMessageResponse = Network.SOGS.SendDirectMessageResponse( + id: 1, + sender: TestConstants.blind15PublicKey, + recipient: "testRecipient", + posted: 1122, + expires: 2233 + ) +} + +extension Network.SOGS.DirectMessage: Mocked { + static var mock: Network.SOGS.DirectMessage = Network.SOGS.DirectMessage( + id: 101, + sender: TestConstants.blind15PublicKey, + recipient: "testRecipient", + posted: 1212, + expires: 2323, + base64EncodedMessage: "TestMessage".data(using: .utf8)!.base64EncodedString() + ) +} diff --git a/SessionSnodeKitTests/_TestUtilities/MockNetwork.swift b/SessionNetworkingKitTests/_TestUtilities/MockNetwork.swift similarity index 98% rename from SessionSnodeKitTests/_TestUtilities/MockNetwork.swift rename to SessionNetworkingKitTests/_TestUtilities/MockNetwork.swift index efecb6d9ac..eff21161ac 100644 --- a/SessionSnodeKitTests/_TestUtilities/MockNetwork.swift +++ b/SessionNetworkingKitTests/_TestUtilities/MockNetwork.swift @@ -4,7 +4,7 @@ import Foundation import Combine import SessionUtilitiesKit -@testable import SessionSnodeKit +@testable import SessionNetworkingKit // MARK: - MockNetwork @@ -47,7 +47,7 @@ class MockNetwork: Mock, NetworkType { return mock(args: [body, destination, requestTimeout, requestAndPathBuildTimeout]) } - func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, AppVersionResponse), Error> { + func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, Network.FileServer.AppVersionResponse), Error> { return mock(args: [ed25519SecretKey]) } } diff --git a/SessionSnodeKitTests/_TestUtilities/MockSnodeAPICache.swift b/SessionNetworkingKitTests/_TestUtilities/MockSnodeAPICache.swift similarity index 97% rename from SessionSnodeKitTests/_TestUtilities/MockSnodeAPICache.swift rename to SessionNetworkingKitTests/_TestUtilities/MockSnodeAPICache.swift index 75111962b8..90be7933ed 100644 --- a/SessionSnodeKitTests/_TestUtilities/MockSnodeAPICache.swift +++ b/SessionNetworkingKitTests/_TestUtilities/MockSnodeAPICache.swift @@ -6,7 +6,7 @@ import Foundation import Combine import SessionUtilitiesKit -@testable import SessionSnodeKit +@testable import SessionNetworkingKit class MockSnodeAPICache: Mock, SnodeAPICacheType { var hardfork: Int { diff --git a/SessionNotificationServiceExtension/NotificationResolution.swift b/SessionNotificationServiceExtension/NotificationResolution.swift index aef0805a55..c291628c84 100644 --- a/SessionNotificationServiceExtension/NotificationResolution.swift +++ b/SessionNotificationServiceExtension/NotificationResolution.swift @@ -4,11 +4,12 @@ import Foundation import SessionUIKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit enum NotificationResolution: CustomStringConvertible { - case success(PushNotificationAPI.NotificationMetadata) + case success(Network.PushNotification.NotificationMetadata) case successCall case ignoreDueToMainAppRunning @@ -20,15 +21,15 @@ enum NotificationResolution: CustomStringConvertible { case ignoreDueToMessageRequest case ignoreDueToDuplicateMessage case ignoreDueToDuplicateCall - case ignoreDueToContentSize(PushNotificationAPI.NotificationMetadata) + case ignoreDueToContentSize(Network.PushNotification.NotificationMetadata) case errorTimeout case errorNotReadyForExtensions case errorLegacyPushNotification case errorCallFailure - case errorNoContent(PushNotificationAPI.NotificationMetadata) - case errorProcessing(PushNotificationAPI.ProcessResult) - case errorMessageHandling(MessageReceiverError, PushNotificationAPI.NotificationMetadata) + case errorNoContent(Network.PushNotification.NotificationMetadata) + case errorProcessing(Network.PushNotification.ProcessResult) + case errorMessageHandling(MessageReceiverError, Network.PushNotification.NotificationMetadata) case errorOther(Error) public var description: String { @@ -88,7 +89,7 @@ enum NotificationResolution: CustomStringConvertible { } } -internal extension PushNotificationAPI.NotificationMetadata { +internal extension Network.PushNotification.NotificationMetadata { var messageOriginString: String { guard self != .invalid else { return "decryption failure" } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 295ebf2071..54fc5cd9a9 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -5,7 +5,7 @@ import CallKit import UserNotifications import SessionUIKit import SessionMessagingKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit // MARK: - Log.Category @@ -157,7 +157,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // MARK: - Notification Handling private func extractNotificationInfo(_ info: NotificationInfo) throws -> NotificationInfo { - let (maybeData, metadata, result) = PushNotificationAPI.processNotification( + let (maybeData, metadata, result) = Network.PushNotification.processNotification( notificationContent: info.content, using: dependencies ) @@ -279,7 +279,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension private func handleConfigMessage( _ notification: ProcessedNotification, swarmPublicKey: String, - namespace: SnodeAPI.Namespace, + namespace: Network.SnodeAPI.Namespace, serverHash: String, serverTimestampMs: Int64, data: Data @@ -806,7 +806,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension // // TODO: [Database Relocation] Need to de-database the 'preparedSubscribe' call for this to work (neeeds the AuthMethod logic to be de-databased) // /// Since this is an API call we need to wait for it to complete before we trigger the `completeSilently` logic // Log.info(.cat, "Group invitation was auto-approved, attempting to subscribe for PNs.") -// try? PushNotificationAPI +// try? Network.PushNotification // .preparedSubscribe( // db, // token: Data(hex: token), @@ -1284,7 +1284,7 @@ private extension NotificationServiceExtension { let content: UNMutableNotificationContent let requestId: String let contentHandler: ((UNNotificationContent) -> Void) - let metadata: PushNotificationAPI.NotificationMetadata + let metadata: Network.PushNotification.NotificationMetadata let data: Data let mainAppUnreadCount: Int @@ -1292,7 +1292,7 @@ private extension NotificationServiceExtension { requestId: String? = nil, content: UNMutableNotificationContent? = nil, contentHandler: ((UNNotificationContent) -> Void)? = nil, - metadata: PushNotificationAPI.NotificationMetadata? = nil, + metadata: Network.PushNotification.NotificationMetadata? = nil, mainAppUnreadCount: Int? = nil ) -> NotificationInfo { return NotificationInfo( @@ -1316,8 +1316,8 @@ private extension NotificationServiceExtension { enum NotificationError: Error { case notReadyForExtension - case processingErrorWithFallback(PushNotificationAPI.ProcessResult, PushNotificationAPI.NotificationMetadata) - case processingError(PushNotificationAPI.ProcessResult, PushNotificationAPI.NotificationMetadata) + case processingErrorWithFallback(Network.PushNotification.ProcessResult, Network.PushNotification.NotificationMetadata) + case processingError(Network.PushNotification.ProcessResult, Network.PushNotification.NotificationMetadata) case timeout } } diff --git a/SessionShareExtension/ShareNavController.swift b/SessionShareExtension/ShareNavController.swift index 13b036416a..b1f420316f 100644 --- a/SessionShareExtension/ShareNavController.swift +++ b/SessionShareExtension/ShareNavController.swift @@ -7,7 +7,7 @@ import CoreServices import UniformTypeIdentifiers import SignalUtilitiesKit import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit import SessionMessagingKit @@ -45,7 +45,6 @@ final class ShareNavController: UINavigationController { dependencies.warmCache(cache: .appVersion) AppSetup.setupEnvironment( - additionalMigrationTargets: [DeprecatedUIKitMigrationTarget.self], appSpecificBlock: { [dependencies] in // stringlint:ignore_start if !Log.loggerExists(withPrefix: "SessionShareExtension") { diff --git a/SessionShareExtension/ThreadPickerVC.swift b/SessionShareExtension/ThreadPickerVC.swift index 34246413a9..60295606b9 100644 --- a/SessionShareExtension/ThreadPickerVC.swift +++ b/SessionShareExtension/ThreadPickerVC.swift @@ -8,7 +8,7 @@ import DifferenceKit import SessionUIKit import SignalUtilitiesKit import SessionMessagingKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableViewDelegate, AttachmentApprovalViewControllerDelegate, ThemedNavigation { @@ -275,7 +275,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView dependencies[singleton: .network] .getSwarm(for: swarmPublicKey) .tryFlatMapWithRandomSnode(using: dependencies) { snode in - try SnodeAPI + try Network.SnodeAPI .preparedGetNetworkTime(from: snode, using: dependencies) .send(using: dependencies) } diff --git a/SessionSnodeKit/Configuration.swift b/SessionSnodeKit/Configuration.swift deleted file mode 100644 index af6ed9cc8d..0000000000 --- a/SessionSnodeKit/Configuration.swift +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import GRDB -import SessionUtilitiesKit - -public enum SNSnodeKit: MigratableTarget { // Just to make the external API nice - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .snodeKit, - migrations: [ - [ - _001_InitialSetupMigration.self, - _002_SetupStandardJobs.self - ], // Initial DB Creation - [ - _003_YDBToGRDBMigration.self - ], // YDB to GRDB Migration - [ - _004_FlagMessageHashAsDeletedOrInvalid.self - ], // Legacy DB removal - [], // Add job priorities - [], // Fix thread FTS - [ - _005_AddSnodeReveivedMessageInfoPrimaryKey.self, - _006_DropSnodeCache.self, - _007_SplitSnodeReceivedMessageInfo.self, - _008_ResetUserConfigLastHashes.self - ], - [], // Renamed `Setting` to `KeyValueStore` - [] - ] - ) - } -} diff --git a/SessionSnodeKit/Meta/SessionSnodeKit.h b/SessionSnodeKit/Meta/SessionSnodeKit.h deleted file mode 100644 index 698aa516fd..0000000000 --- a/SessionSnodeKit/Meta/SessionSnodeKit.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -FOUNDATION_EXPORT double SessionSnodeKitVersionNumber; -FOUNDATION_EXPORT const unsigned char SessionSnodeKitVersionString[]; diff --git a/SessionSnodeKit/Models/AppVersionResponse.swift b/SessionSnodeKit/Models/AppVersionResponse.swift deleted file mode 100644 index ae5c33739b..0000000000 --- a/SessionSnodeKit/Models/AppVersionResponse.swift +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -public class AppVersionResponse: AppVersionInfo { - enum CodingKeys: String, CodingKey { - case prerelease - } - - public let prerelease: AppVersionInfo? - - public init( - version: String, - updated: TimeInterval?, - name: String?, - notes: String?, - assets: [Asset]?, - prerelease: AppVersionInfo? - ) { - self.prerelease = prerelease - - super.init( - version: version, - updated: updated, - name: name, - notes: notes, - assets: assets - ) - } - - required init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) - - self.prerelease = try? container.decode(AppVersionInfo?.self, forKey: .prerelease) - - try super.init(from: decoder) - } - - public override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - try container.encodeIfPresent(prerelease, forKey: .prerelease) - } -} - -// MARK: - AppVersionInfo - -public class AppVersionInfo: Codable { - enum CodingKeys: String, CodingKey { - case version = "result" - case updated - case name - case notes - case assets - } - - public struct Asset: Codable { - enum CodingKeys: String, CodingKey { - case name - case url - } - - public let name: String - public let url: String - } - - public let version: String - public let updated: TimeInterval? - public let name: String? - public let notes: String? - public let assets: [Asset]? - - public init( - version: String, - updated: TimeInterval?, - name: String?, - notes: String?, - assets: [Asset]? - ) { - self.version = version - self.updated = updated - self.name = name - self.notes = notes - self.assets = assets - } - - public func encode(to encoder: Encoder) throws { - var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(version, forKey: .version) - try container.encodeIfPresent(updated, forKey: .updated) - try container.encodeIfPresent(name, forKey: .name) - try container.encodeIfPresent(notes, forKey: .notes) - try container.encodeIfPresent(assets, forKey: .assets) - } -} diff --git a/SessionSnodeKit/Models/OxenDaemonRPCRequest.swift b/SessionSnodeKit/Models/OxenDaemonRPCRequest.swift deleted file mode 100644 index 1b9d6ea453..0000000000 --- a/SessionSnodeKit/Models/OxenDaemonRPCRequest.swift +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -public struct OxenDaemonRPCRequest: Encodable { - private enum CodingKeys: String, CodingKey { - case endpoint - case body = "params" - } - - private let endpoint: String - private let body: T - - public init( - endpoint: SnodeAPI.Endpoint, - body: T - ) { - self.endpoint = endpoint.path - self.body = body - } -} diff --git a/SessionSnodeKit/SnodeAPI/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI/SnodeAPI.swift deleted file mode 100644 index 71bbe5bd41..0000000000 --- a/SessionSnodeKit/SnodeAPI/SnodeAPI.swift +++ /dev/null @@ -1,857 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -// -// stringlint:disable - -import Foundation -import Combine -import GRDB -import Punycode -import SessionUtilitiesKit - -public final class SnodeAPI { - // MARK: - Settings - - public static let maxRetryCount: Int = 8 - - // MARK: - Batching & Polling - - public typealias PollResponse = [SnodeAPI.Namespace: (info: ResponseInfoType, data: PreparedGetMessagesResponse?)] - - public static func preparedPoll( - _ db: ObservingDatabase, - namespaces: [SnodeAPI.Namespace], - refreshingConfigHashes: [String] = [], - from snode: LibSession.Snode, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - // Determine the maxSize each namespace in the request should take up - var requests: [any ErasedPreparedRequest] = [] - let namespaceMaxSizeMap: [SnodeAPI.Namespace: Int64] = SnodeAPI.Namespace.maxSizeMap(for: namespaces) - let fallbackSize: Int64 = (namespaceMaxSizeMap.values.min() ?? 1) - - // If we have any config hashes to refresh TTLs then add those requests first - if !refreshingConfigHashes.isEmpty { - let updatedExpiryMS: Int64 = ( - dependencies[cache: .snodeAPI].currentOffsetTimestampMs() + - (30 * 24 * 60 * 60 * 1000) // 30 days - ) - requests.append( - try SnodeAPI.preparedUpdateExpiry( - serverHashes: refreshingConfigHashes, - updatedExpiryMs: updatedExpiryMS, - extendOnly: true, - ignoreValidationFailure: true, - explicitTargetNode: snode, - authMethod: authMethod, - using: dependencies - ) - ) - } - - // Add the various 'getMessages' requests - requests.append( - contentsOf: try namespaces.map { namespace -> any ErasedPreparedRequest in - try SnodeAPI.preparedGetMessages( - db, - namespace: namespace, - snode: snode, - maxSize: namespaceMaxSizeMap[namespace] - .defaulting(to: fallbackSize), - authMethod: authMethod, - using: dependencies - ) - } - ) - - return try preparedBatch( - requests: requests, - requireAllBatchResponses: true, - snode: snode, - swarmPublicKey: try authMethod.swarmPublicKey, - using: dependencies - ) - .map { (_: ResponseInfoType, batchResponse: Network.BatchResponse) -> [SnodeAPI.Namespace: (info: ResponseInfoType, data: PreparedGetMessagesResponse?)] in - let messageResponses: [Network.BatchSubResponse] = batchResponse - .compactMap { $0 as? Network.BatchSubResponse } - - return zip(namespaces, messageResponses) - .reduce(into: [:]) { result, next in - guard let messageResponse: PreparedGetMessagesResponse = next.1.body else { return } - - result[next.0] = (next.1, messageResponse) - } - } - } - - public static func preparedBatch( - requests: [any ErasedPreparedRequest], - requireAllBatchResponses: Bool, - snode: LibSession.Snode? = nil, - swarmPublicKey: String, - requestTimeout: TimeInterval = Network.defaultTimeout, - requestAndPathBuildTimeout: TimeInterval? = nil, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try SnodeAPI - .prepareRequest( - request: { - switch snode { - case .none: - return try Request( - endpoint: .batch, - swarmPublicKey: swarmPublicKey, - body: Network.BatchRequest(requestsKey: .requests, requests: requests) - ) - - case .some(let snode): - return try Request( - endpoint: .batch, - snode: snode, - swarmPublicKey: swarmPublicKey, - body: Network.BatchRequest(requestsKey: .requests, requests: requests) - ) - } - }(), - responseType: Network.BatchResponse.self, - requireAllBatchResponses: requireAllBatchResponses, - requestTimeout: requestTimeout, - requestAndPathBuildTimeout: requestAndPathBuildTimeout, - using: dependencies - ) - } - - public static func preparedSequence( - requests: [any ErasedPreparedRequest], - requireAllBatchResponses: Bool, - swarmPublicKey: String, - snodeRetrievalRetryCount: Int, - requestTimeout: TimeInterval = Network.defaultTimeout, - requestAndPathBuildTimeout: TimeInterval? = nil, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try SnodeAPI - .prepareRequest( - request: Request( - endpoint: .sequence, - swarmPublicKey: swarmPublicKey, - body: Network.BatchRequest(requestsKey: .requests, requests: requests), - snodeRetrievalRetryCount: snodeRetrievalRetryCount - ), - responseType: Network.BatchResponse.self, - requireAllBatchResponses: requireAllBatchResponses, - requestTimeout: requestTimeout, - requestAndPathBuildTimeout: requestAndPathBuildTimeout, - using: dependencies - ) - } - - // MARK: - Retrieve - - public typealias PreparedGetMessagesResponse = (messages: [SnodeReceivedMessage], lastHash: String?) - - public static func preparedGetMessages( - _ db: ObservingDatabase, - namespace: SnodeAPI.Namespace, - snode: LibSession.Snode, - maxSize: Int64? = nil, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - let maybeLastHash: String? = try SnodeReceivedMessageInfo - .fetchLastNotExpired( - db, - for: snode, - namespace: namespace, - swarmPublicKey: try authMethod.swarmPublicKey, - using: dependencies - )? - .hash - let preparedRequest: Network.PreparedRequest = try { - // Check if this namespace requires authentication - guard namespace.requiresReadAuthentication else { - return try SnodeAPI.prepareRequest( - request: Request( - endpoint: .getMessages, - swarmPublicKey: try authMethod.swarmPublicKey, - body: LegacyGetMessagesRequest( - pubkey: try authMethod.swarmPublicKey, - lastHash: (maybeLastHash ?? ""), - namespace: namespace, - maxCount: nil, - maxSize: maxSize - ) - ), - responseType: GetMessagesResponse.self, - using: dependencies - ) - } - - return try SnodeAPI.prepareRequest( - request: Request( - endpoint: .getMessages, - swarmPublicKey: try authMethod.swarmPublicKey, - body: GetMessagesRequest( - lastHash: (maybeLastHash ?? ""), - namespace: namespace, - authMethod: authMethod, - timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs(), - maxSize: maxSize - ) - ), - responseType: GetMessagesResponse.self, - using: dependencies - ) - }() - - return preparedRequest - .tryMap { _, response -> (messages: [SnodeReceivedMessage], lastHash: String?) in - return ( - try response.messages.compactMap { rawMessage -> SnodeReceivedMessage? in - SnodeReceivedMessage( - snode: snode, - publicKey: try authMethod.swarmPublicKey, - namespace: namespace, - rawMessage: rawMessage - ) - }, - maybeLastHash - ) - } - } - - public static func getSessionID( - for onsName: String, - using dependencies: Dependencies - ) -> AnyPublisher { - let validationCount = 3 - - // The name must be lowercased - let onsName = onsName.lowercased().idnaEncoded ?? onsName.lowercased() - - // Hash the ONS name using BLAKE2b - guard - let nameHash = dependencies[singleton: .crypto].generate( - .hash(message: Array(onsName.utf8)) - ) - else { - return Fail(error: SnodeAPIError.onsHashingFailed) - .eraseToAnyPublisher() - } - - // Ask 3 different snodes for the Session ID associated with the given name hash - let base64EncodedNameHash = nameHash.toBase64() - - return dependencies[singleton: .network] - .getRandomNodes(count: validationCount) - .tryFlatMap { nodes in - Publishers.MergeMany( - try nodes.map { snode in - try SnodeAPI - .prepareRequest( - request: Request( - endpoint: .oxenDaemonRPCCall, - snode: snode, - body: OxenDaemonRPCRequest( - endpoint: .daemonOnsResolve, - body: ONSResolveRequest( - type: 0, // type 0 means Session - base64EncodedNameHash: base64EncodedNameHash - ) - ) - ), - responseType: ONSResolveResponse.self, - using: dependencies - ) - .tryMap { _, response -> String in - try dependencies[singleton: .crypto].tryGenerate( - .sessionId(name: onsName, response: response) - ) - } - .send(using: dependencies) - .map { _, sessionId in sessionId } - .eraseToAnyPublisher() - } - ) - } - .collect() - .tryMap { results -> String in - guard results.count == validationCount, Set(results).count == 1 else { - throw SnodeAPIError.onsValidationFailed - } - - return results[0] - } - .eraseToAnyPublisher() - } - - public static func preparedGetExpiries( - of serverHashes: [String], - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try SnodeAPI - .prepareRequest( - request: Request( - endpoint: .getExpiries, - swarmPublicKey: try authMethod.swarmPublicKey, - body: GetExpiriesRequest( - messageHashes: serverHashes, - authMethod: authMethod, - timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() - ) - ), - responseType: GetExpiriesResponse.self, - using: dependencies - ) - } - - // MARK: - Store - - public static func preparedSendMessage( - message: SnodeMessage, - in namespace: Namespace, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - let request: Network.PreparedRequest = try { - // Check if this namespace requires authentication - guard namespace.requiresWriteAuthentication else { - return try SnodeAPI.prepareRequest( - request: Request( - endpoint: .sendMessage, - swarmPublicKey: try authMethod.swarmPublicKey, - body: LegacySendMessagesRequest( - message: message, - namespace: namespace - ), - snodeRetrievalRetryCount: 0 // The SendMessageJob already has a retry mechanism - ), - responseType: SendMessagesResponse.self, - requestAndPathBuildTimeout: Network.defaultTimeout, - using: dependencies - ) - } - - return try SnodeAPI.prepareRequest( - request: Request( - endpoint: .sendMessage, - swarmPublicKey: try authMethod.swarmPublicKey, - body: SendMessageRequest( - message: message, - namespace: namespace, - authMethod: authMethod, - timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() - ), - snodeRetrievalRetryCount: 0 // The SendMessageJob already has a retry mechanism - ), - responseType: SendMessagesResponse.self, - requestAndPathBuildTimeout: Network.defaultTimeout, - using: dependencies - ) - }() - - return request - .tryMap { _, response -> SendMessagesResponse in - try response.validateResultMap( - swarmPublicKey: try authMethod.swarmPublicKey, - using: dependencies - ) - - return response - } - } - - // MARK: - Edit - - public static func preparedUpdateExpiry( - serverHashes: [String], - updatedExpiryMs: Int64, - shortenOnly: Bool? = nil, - extendOnly: Bool? = nil, - ignoreValidationFailure: Bool = false, - explicitTargetNode: LibSession.Snode? = nil, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[String: UpdateExpiryResponseResult]> { - // ShortenOnly and extendOnly cannot be true at the same time - guard shortenOnly == nil || extendOnly == nil else { throw NetworkError.invalidPreparedRequest } - - return try SnodeAPI - .prepareRequest( - request: Request( - endpoint: .expire, - swarmPublicKey: try authMethod.swarmPublicKey, - body: UpdateExpiryRequest( - messageHashes: serverHashes, - expiryMs: UInt64(updatedExpiryMs), - shorten: shortenOnly, - extend: extendOnly, - authMethod: authMethod - ) - ), - responseType: UpdateExpiryResponse.self, - using: dependencies - ) - .tryMap { _, response -> [String: UpdateExpiryResponseResult] in - do { - return try response.validResultMap( - swarmPublicKey: try authMethod.swarmPublicKey, - validationData: serverHashes, - using: dependencies - ) - } - catch { - guard ignoreValidationFailure else { throw error } - - return [:] - } - } - .handleEvents( - receiveOutput: { _, result in - /// Since we have updated the TTL we need to make sure we also update the local - /// `SnodeReceivedMessageInfo.expirationDateMs` values so they match the updated swarm, if - /// we had a specific `snode` we we're sending the request to then we should use those values, otherwise - /// we can just grab the first value from the response and use that - let maybeTargetResult: UpdateExpiryResponseResult? = { - guard let snode: LibSession.Snode = explicitTargetNode else { - return result.first?.value - } - - return result[snode.ed25519PubkeyHex] - }() - guard - let targetResult: UpdateExpiryResponseResult = maybeTargetResult, - let groupedExpiryResult: [UInt64: [String]] = targetResult.changed - .updated(with: targetResult.unchanged) - .groupedByValue() - .nullIfEmpty - else { return } - - dependencies[singleton: .storage].writeAsync { db in - try groupedExpiryResult.forEach { updatedExpiry, hashes in - try SnodeReceivedMessageInfo - .filter(hashes.contains(SnodeReceivedMessageInfo.Columns.hash)) - .updateAll( - db, - SnodeReceivedMessageInfo.Columns.expirationDateMs - .set(to: updatedExpiry) - ) - } - } - } - ) - } - - public static func preparedRevokeSubaccounts( - subaccountsToRevoke: [[UInt8]], - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - let timestampMs: UInt64 = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() - - return try SnodeAPI - .prepareRequest( - request: Request( - endpoint: .revokeSubaccount, - swarmPublicKey: try authMethod.swarmPublicKey, - body: RevokeSubaccountRequest( - subaccountsToRevoke: subaccountsToRevoke, - authMethod: authMethod, - timestampMs: timestampMs - ) - ), - responseType: RevokeSubaccountResponse.self, - using: dependencies - ) - .tryMap { _, response -> Void in - try response.validateResultMap( - swarmPublicKey: try authMethod.swarmPublicKey, - validationData: (subaccountsToRevoke, timestampMs), - using: dependencies - ) - - return () - } - } - - public static func preparedUnrevokeSubaccounts( - subaccountsToUnrevoke: [[UInt8]], - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - let timestampMs: UInt64 = dependencies[cache: .snodeAPI].currentOffsetTimestampMs() - - return try SnodeAPI - .prepareRequest( - request: Request( - endpoint: .unrevokeSubaccount, - swarmPublicKey: try authMethod.swarmPublicKey, - body: UnrevokeSubaccountRequest( - subaccountsToUnrevoke: subaccountsToUnrevoke, - authMethod: authMethod, - timestampMs: timestampMs - ) - ), - responseType: UnrevokeSubaccountResponse.self, - using: dependencies - ) - .tryMap { _, response -> Void in - try response.validateResultMap( - swarmPublicKey: try authMethod.swarmPublicKey, - validationData: (subaccountsToUnrevoke, timestampMs), - using: dependencies - ) - - return () - } - } - - // MARK: - Delete - - public static func preparedDeleteMessages( - serverHashes: [String], - requireSuccessfulDeletion: Bool, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[String: Bool]> { - return try SnodeAPI - .prepareRequest( - request: Request( - endpoint: .deleteMessages, - swarmPublicKey: try authMethod.swarmPublicKey, - body: DeleteMessagesRequest( - messageHashes: serverHashes, - requireSuccessfulDeletion: requireSuccessfulDeletion, - authMethod: authMethod - ) - ), - responseType: DeleteMessagesResponse.self, - using: dependencies - ) - .tryMap { _, response -> [String: Bool] in - let validResultMap: [String: Bool] = try response.validResultMap( - swarmPublicKey: try authMethod.swarmPublicKey, - validationData: serverHashes, - using: dependencies - ) - - // If `validResultMap` didn't throw then at least one service node - // deleted successfully so we should mark the hash as invalid so we - // don't try to fetch updates using that hash going forward (if we - // do we would end up re-fetching all old messages) - dependencies[singleton: .storage].writeAsync { db in - try? SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( - db, - potentiallyInvalidHashes: serverHashes - ) - } - - return validResultMap - } - } - - - /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. - public static func preparedDeleteAllMessages( - namespace: SnodeAPI.Namespace, - requestTimeout: TimeInterval = Network.defaultTimeout, - requestAndPathBuildTimeout: TimeInterval? = nil, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[String: Bool]> { - return try SnodeAPI - .prepareRequest( - request: Request( - endpoint: .deleteAll, - swarmPublicKey: try authMethod.swarmPublicKey, - requiresLatestNetworkTime: true, - body: DeleteAllMessagesRequest( - namespace: namespace, - authMethod: authMethod, - timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() - ), - snodeRetrievalRetryCount: 0 - ), - responseType: DeleteAllMessagesResponse.self, - requestTimeout: requestTimeout, - requestAndPathBuildTimeout: requestAndPathBuildTimeout, - using: dependencies - ) - .tryMap { info, response -> [String: Bool] in - guard let targetInfo: LatestTimestampResponseInfo = info as? LatestTimestampResponseInfo else { - throw NetworkError.invalidResponse - } - - return try response.validResultMap( - swarmPublicKey: try authMethod.swarmPublicKey, - validationData: targetInfo.timestampMs, - using: dependencies - ) - } - } - - /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. - public static func preparedDeleteAllMessages( - beforeMs: UInt64, - namespace: SnodeAPI.Namespace, - authMethod: AuthenticationMethod, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest<[String: Bool]> { - return try SnodeAPI - .prepareRequest( - request: Request( - endpoint: .deleteAllBefore, - swarmPublicKey: try authMethod.swarmPublicKey, - requiresLatestNetworkTime: true, - body: DeleteAllBeforeRequest( - beforeMs: beforeMs, - namespace: namespace, - authMethod: authMethod, - timestampMs: dependencies[cache: .snodeAPI].currentOffsetTimestampMs() - ) - ), - responseType: DeleteAllMessagesResponse.self, - retryCount: maxRetryCount, - using: dependencies - ) - .tryMap { _, response -> [String: Bool] in - try response.validResultMap( - swarmPublicKey: try authMethod.swarmPublicKey, - validationData: beforeMs, - using: dependencies - ) - } - } - - // MARK: - Internal API - - public static func preparedGetNetworkTime( - from snode: LibSession.Snode, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try SnodeAPI - .prepareRequest( - request: Request, Endpoint>( - endpoint: .getInfo, - snode: snode, - body: [:] - ), - responseType: GetNetworkTimestampResponse.self, - using: dependencies - ) - .map { _, response in - // Assume we've fetched the networkTime in order to send a message to the specified snode, in - // which case we want to update the 'clockOffsetMs' value for subsequent requests - let offset = (Int64(response.timestamp) - Int64(floor(dependencies.dateNow.timeIntervalSince1970 * 1000))) - dependencies.mutate(cache: .snodeAPI) { $0.setClockOffsetMs(offset) } - - return response.timestamp - } - } - - // MARK: - Convenience - - private static func prepareRequest( - request: Request, - responseType: R.Type, - requireAllBatchResponses: Bool = true, - retryCount: Int = 0, - requestTimeout: TimeInterval = Network.defaultTimeout, - requestAndPathBuildTimeout: TimeInterval? = nil, - using dependencies: Dependencies - ) throws -> Network.PreparedRequest { - return try Network.PreparedRequest( - request: request, - responseType: responseType, - requireAllBatchResponses: requireAllBatchResponses, - retryCount: retryCount, - requestTimeout: requestTimeout, - requestAndPathBuildTimeout: requestAndPathBuildTimeout, - using: dependencies - ) - .handleEvents( - receiveOutput: { _, response in - switch response { - case let snodeResponse as SnodeResponse: - // Update the network offset based on the response so subsequent requests have - // the correct network offset time - let offset = (Int64(snodeResponse.timeOffset) - Int64(floor(dependencies.dateNow.timeIntervalSince1970 * 1000))) - dependencies.mutate(cache: .snodeAPI) { - $0.setClockOffsetMs(offset) - - // Extract and store hard fork information if returned - guard snodeResponse.hardForkVersion.count > 1 else { return } - - if snodeResponse.hardForkVersion[1] > $0.softfork { - $0.softfork = snodeResponse.hardForkVersion[1] - dependencies[defaults: .standard, key: .softfork] = $0.softfork - } - - if snodeResponse.hardForkVersion[0] > $0.hardfork { - $0.hardfork = snodeResponse.hardForkVersion[0] - dependencies[defaults: .standard, key: .hardfork] = $0.hardfork - $0.softfork = snodeResponse.hardForkVersion[1] - dependencies[defaults: .standard, key: .softfork] = $0.softfork - } - } - - default: break - } - } - ) - } -} - -// MARK: - Publisher Convenience - -public extension Publisher where Output == Set { - func tryMapWithRandomSnode( - using dependencies: Dependencies, - _ transform: @escaping (LibSession.Snode) throws -> T - ) -> AnyPublisher { - return self - .tryMap { swarm -> T in - var remainingSnodes: Set = swarm - let snode: LibSession.Snode = try dependencies.popRandomElement(&remainingSnodes) ?? { - throw SnodeAPIError.insufficientSnodes - }() - - return try transform(snode) - } - .eraseToAnyPublisher() - } - - func tryFlatMapWithRandomSnode( - maxPublishers: Subscribers.Demand = .unlimited, - retry retries: Int = 0, - drainBehaviour: ThreadSafeObject = .alwaysRandom, - using dependencies: Dependencies, - _ transform: @escaping (LibSession.Snode) throws -> P - ) -> AnyPublisher where T == P.Output, P: Publisher, P.Failure == Error { - return self - .mapError { $0 } - .flatMap(maxPublishers: maxPublishers) { swarm -> AnyPublisher in - // If we don't want to reuse a specific snode multiple times then just grab a - // random one from the swarm every time - var remainingSnodes: Set = drainBehaviour.performUpdateAndMap { behaviour in - switch behaviour { - case .alwaysRandom: return (behaviour, swarm) - case .limitedReuse(_, let targetSnode, _, let usedSnodes, let swarmHash): - // If we've used all of the snodes or the swarm has changed then reset the used list - guard swarmHash == swarm.hashValue && (targetSnode != nil || usedSnodes != swarm) else { - return (behaviour.reset(), swarm) - } - - return (behaviour, swarm.subtracting(usedSnodes)) - } - } - var lastError: Error? - - return Just(()) - .setFailureType(to: Error.self) - .tryFlatMap(maxPublishers: maxPublishers) { _ -> AnyPublisher in - let snode: LibSession.Snode = try drainBehaviour.performUpdateAndMap { behaviour in - switch behaviour { - case .limitedReuse(_, .some(let targetSnode), _, _, _): - return (behaviour.use(snode: targetSnode, from: swarm), targetSnode) - default: break - } - - // Select the next snode - let result: LibSession.Snode = try dependencies.popRandomElement(&remainingSnodes) ?? { - throw SnodeAPIError.ranOutOfRandomSnodes(lastError) - }() - - return (behaviour.use(snode: result, from: swarm), result) - } - - return try transform(snode) - .eraseToAnyPublisher() - } - .mapError { error in - // Prevent nesting the 'ranOutOfRandomSnodes' errors - switch error { - case SnodeAPIError.ranOutOfRandomSnodes: break - default: lastError = error - } - - return error - } - .retry(retries) - .eraseToAnyPublisher() - } - .eraseToAnyPublisher() - } -} - -// MARK: - SnodeAPI Cache - -public extension SnodeAPI { - class Cache: SnodeAPICacheType { - private let dependencies: Dependencies - public var hardfork: Int - public var softfork: Int - public var clockOffsetMs: Int64 = 0 - - init(using dependencies: Dependencies) { - self.dependencies = dependencies - self.hardfork = dependencies[defaults: .standard, key: .hardfork] - self.softfork = dependencies[defaults: .standard, key: .softfork] - } - - public func currentOffsetTimestampMs() -> T { - let timestampNowMs: Int64 = (Int64(floor(dependencies.dateNow.timeIntervalSince1970 * 1000)) + clockOffsetMs) - - guard let convertedTimestampNowMs: T = T(exactly: timestampNowMs) else { - Log.critical("[SnodeAPI.Cache] Failed to convert the timestamp to the desired type: \(type(of: T.self)).") - return 0 - } - - return convertedTimestampNowMs - } - - public func setClockOffsetMs(_ clockOffsetMs: Int64) { - self.clockOffsetMs = clockOffsetMs - } - } -} - -public extension Cache { - static let snodeAPI: CacheConfig = Dependencies.create( - identifier: "snodeAPI", - createInstance: { dependencies in SnodeAPI.Cache(using: dependencies) }, - mutableInstance: { $0 }, - immutableInstance: { $0 } - ) -} - -// MARK: - SnodeAPICacheType - -/// This is a read-only version of the Cache designed to avoid unintentionally mutating the instance in a non-thread-safe way -public protocol SnodeAPIImmutableCacheType: ImmutableCacheType { - /// The last seen storage server hard fork version. - var hardfork: Int { get } - - /// The last seen storage server soft fork version. - var softfork: Int { get } - - /// The offset between the user's clock and the Service Node's clock. Used in cases where the - /// user's clock is incorrect. - var clockOffsetMs: Int64 { get } - - /// Tthe current user clock timestamp in milliseconds offset by the difference between the user's clock and the clock of the most - /// recent Service Node's that was communicated with. - func currentOffsetTimestampMs() -> T -} - -public protocol SnodeAPICacheType: SnodeAPIImmutableCacheType, MutableCacheType { - /// The last seen storage server hard fork version. - var hardfork: Int { get set } - - /// The last seen storage server soft fork version. - var softfork: Int { get set } - - /// A function to update the offset between the user's clock and the Service Node's clock. - func setClockOffsetMs(_ clockOffsetMs: Int64) -} diff --git a/SessionSnodeKit/Types/Network.swift b/SessionSnodeKit/Types/Network.swift deleted file mode 100644 index 5913775129..0000000000 --- a/SessionSnodeKit/Types/Network.swift +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. -// -// stringlint:disable - -import Foundation -import Combine -import SessionUtilitiesKit - -// MARK: - Singleton - -public extension Singleton { - static let network: SingletonConfig = Dependencies.create( - identifier: "network", - createInstance: { dependencies in LibSessionNetwork(using: dependencies) } - ) -} - -// MARK: - NetworkType - -public protocol NetworkType { - func getSwarm(for swarmPublicKey: String) -> AnyPublisher, Error> - func getRandomNodes(count: Int) -> AnyPublisher, Error> - - func send( - _ body: Data?, - to destination: Network.Destination, - requestTimeout: TimeInterval, - requestAndPathBuildTimeout: TimeInterval? - ) -> AnyPublisher<(ResponseInfoType, Data?), Error> - - func checkClientVersion(ed25519SecretKey: [UInt8]) -> AnyPublisher<(ResponseInfoType, AppVersionResponse), Error> -} - -// MARK: - Network Constants - -public class Network { - public static let defaultTimeout: TimeInterval = 10 - public static let fileUploadTimeout: TimeInterval = 60 - public static let fileDownloadTimeout: TimeInterval = 30 - - /// **Note:** The max file size is 10,000,000 bytes (rather than 10MiB which would be `(10 * 1024 * 1024)`), 10,000,000 - /// exactly will be fine but a single byte more will result in an error - public static let maxFileSize: UInt = 10_000_000 -} - -// MARK: - NetworkStatus - -public enum NetworkStatus { - case unknown - case connecting - case connected - case disconnected -} - -// MARK: - FileServer Convenience - -public extension Network { - enum NetworkAPI { - static let networkAPIServer = "http://networkv1.getsession.org" - static let networkAPIServerPublicKey = "cbf461a4431dc9174dceef4421680d743a2a0e1a3131fc794240bcb0bc3dd449" - - public enum Endpoint: EndpointType { - case info - case price - case token - - public static var name: String { "NetworkAPI.Endpoint" } - - public var path: String { - switch self { - case .info: return "info" - case .price: return "price" - case .token: return "token" - } - } - } - } - - enum FileServer { - fileprivate static let fileServer = "http://filev2.getsession.org" - fileprivate static let fileServerPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" - fileprivate static let legacyFileServer = "http://88.99.175.227" - fileprivate static let legacyFileServerPublicKey = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" - - public enum Endpoint: EndpointType { - case file - case fileIndividual(String) - case directUrl(URL) - case sessionVersion - - public static var name: String { "FileServerAPI.Endpoint" } - - public var path: String { - switch self { - case .file: return "file" - case .fileIndividual(let fileId): return "file/\(fileId)" - case .directUrl(let url): return url.path.removingPrefix("/") - case .sessionVersion: return "session_version" - } - } - } - - static func fileServerPubkey(url: String? = nil) -> String { - switch url?.contains(legacyFileServer) { - case true: return legacyFileServerPublicKey - default: return fileServerPublicKey - } - } - - static func isFileServerUrl(url: URL) -> Bool { - return ( - url.absoluteString.starts(with: fileServer) || - url.absoluteString.starts(with: legacyFileServer) - ) - } - - public static func downloadUrlString(for url: String, fileId: String) -> String { - switch url.contains(legacyFileServer) { - case true: return "\(fileServer)/\(Endpoint.fileIndividual(fileId).path)" - default: return downloadUrlString(for: fileId) - } - } - - public static func downloadUrlString(for fileId: String) -> String { - return "\(fileServer)/\(Endpoint.fileIndividual(fileId).path)" - } - } - - static func preparedUpload( - data: Data, - requestAndPathBuildTimeout: TimeInterval? = nil, - using dependencies: Dependencies - ) throws -> PreparedRequest { - return try PreparedRequest( - request: Request( - endpoint: FileServer.Endpoint.file, - destination: .serverUpload( - server: FileServer.fileServer, - x25519PublicKey: FileServer.fileServerPublicKey, - fileName: nil - ), - body: data - ), - responseType: FileUploadResponse.self, - requestTimeout: Network.fileUploadTimeout, - requestAndPathBuildTimeout: requestAndPathBuildTimeout, - using: dependencies - ) - } - - static func preparedDownload( - url: URL, - using dependencies: Dependencies - ) throws -> PreparedRequest { - return try PreparedRequest( - request: Request( - endpoint: FileServer.Endpoint.directUrl(url), - destination: .serverDownload( - url: url, - x25519PublicKey: FileServer.fileServerPublicKey, - fileName: nil - ) - ), - responseType: Data.self, - requestTimeout: Network.fileUploadTimeout, - using: dependencies - ) - } -} diff --git a/SessionSnodeKit/Utilities/Threading+SSK.swift b/SessionSnodeKit/Utilities/Threading+SSK.swift deleted file mode 100644 index 3424ad438e..0000000000 --- a/SessionSnodeKit/Utilities/Threading+SSK.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation -import SessionUtilitiesKit - -public extension Threading { - static let workQueue = DispatchQueue(label: "SessionSnodeKit.workQueue", qos: .userInitiated) // It's important that this is a serial queue -} diff --git a/SessionSnodeKitTests/_TestUtilities/CommonSSKMockExtensions.swift b/SessionSnodeKitTests/_TestUtilities/CommonSSKMockExtensions.swift deleted file mode 100644 index 85e05a67af..0000000000 --- a/SessionSnodeKitTests/_TestUtilities/CommonSSKMockExtensions.swift +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import Foundation - -@testable import SessionSnodeKit - -extension NoResponse: Mocked { - static var mock: NoResponse = NoResponse() -} - -extension Network.BatchSubResponse: MockedGeneric where T: Mocked { - typealias Generic = T - - static func mock(type: T.Type) -> Network.BatchSubResponse { - return Network.BatchSubResponse( - code: 200, - headers: [:], - body: Generic.mock, - failedToParseBody: false - ) - } -} - -extension Network.BatchSubResponse { - static func mockArrayValue(type: M.Type) -> Network.BatchSubResponse> { - return Network.BatchSubResponse( - code: 200, - headers: [:], - body: [M.mock], - failedToParseBody: false - ) - } -} - -extension Network.Destination: Mocked { - static var mock: Network.Destination = try! Network.Destination.server( - server: "testServer", - headers: [:], - x25519PublicKey: "" - ).withGeneratedUrl(for: MockEndpoint.mock) -} diff --git a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift index dcf2b6ea26..beaaa7ed26 100644 --- a/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift @@ -5,7 +5,7 @@ import GRDB import Quick import Nimble import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit @@ -21,12 +21,7 @@ class ThreadDisappearingMessagesSettingsViewModelSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try SessionThread( diff --git a/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift index b339cbe090..8d2532b0b0 100644 --- a/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadNotificationSettingsViewModelSpec.swift @@ -5,7 +5,7 @@ import GRDB import Quick import Nimble import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit @@ -21,12 +21,7 @@ class ThreadNotificationSettingsViewModelSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try SessionThread( diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index 5f9fdb51ac..77514dedf8 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -5,7 +5,7 @@ import GRDB import Quick import Nimble import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionUtilitiesKit @testable import SessionUIKit @@ -29,12 +29,7 @@ class ThreadSettingsViewModelSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: SNMessagingKit.migrations, using: dependencies, initialData: { db in try Identity( diff --git a/SessionTests/Database/DatabaseSpec.swift b/SessionTests/Database/DatabaseSpec.swift index 6e0561e696..c9b824b196 100644 --- a/SessionTests/Database/DatabaseSpec.swift +++ b/SessionTests/Database/DatabaseSpec.swift @@ -6,7 +6,7 @@ import Quick import Nimble import SessionUtil import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit @testable import Session @testable import SessionMessagingKit @@ -38,14 +38,7 @@ class DatabaseSpec: QuickSpec { @TestState var initialResult: Result! = nil @TestState var finalResult: Result! = nil - let allMigrations: [Storage.KeyedMigration] = SynchronousStorage.sortedMigrationInfo( - migrationTargets: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ] - ) + let allMigrations: [Migration.Type] = SNMessagingKit.migrations let dynamicTests: [MigrationTest] = MigrationTest.extractTests(allMigrations) let allTableTypes: [(TableRecord & FetchableRecord).Type] = MigrationTest.extractDatabaseTypes(allMigrations) MigrationTest.explicitValues = [ @@ -75,12 +68,7 @@ class DatabaseSpec: QuickSpec { // MARK: -- can be created from an empty state it("can be created from an empty state") { mockStorage.perform( - migrationTargets: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: allMigrations, async: false, onProgressUpdate: nil, onComplete: { result in initialResult = result } @@ -92,7 +80,7 @@ class DatabaseSpec: QuickSpec { // MARK: -- can still parse the database table types it("can still parse the database table types") { mockStorage.perform( - sortedMigrations: allMigrations, + migrations: allMigrations, async: false, onProgressUpdate: nil, onComplete: { result in initialResult = result } @@ -115,7 +103,7 @@ class DatabaseSpec: QuickSpec { // MARK: -- can still parse the database types setting null where possible it("can still parse the database types setting null where possible") { mockStorage.perform( - sortedMigrations: allMigrations, + migrations: allMigrations, async: false, onProgressUpdate: nil, onComplete: { result in initialResult = result } @@ -137,9 +125,9 @@ class DatabaseSpec: QuickSpec { // MARK: -- can migrate from X to Y dynamicTests.forEach { test in - it("can migrate from \(test.initialMigrationKey) to \(test.finalMigrationKey)") { + it("can migrate from \(test.initialMigrationIdentifier) to \(test.finalMigrationIdentifier)") { let initialStateResult: Result = { - if let cachedResult: Result = snapshotCache[test.initialMigrationKey] { + if let cachedResult: Result = snapshotCache[test.initialMigrationIdentifier] { return cachedResult } @@ -153,7 +141,7 @@ class DatabaseSpec: QuickSpec { // Generate dummy data (otherwise structural issues or invalid foreign keys won't error) var initialResult: Result! storage.perform( - sortedMigrations: test.initialMigrations, + migrations: test.initialMigrations, async: false, onProgressUpdate: nil, onComplete: { result in initialResult = result } @@ -163,10 +151,10 @@ class DatabaseSpec: QuickSpec { // Generate dummy data (otherwise structural issues or invalid foreign keys won't error) try MigrationTest.generateDummyData(storage, nullsWherePossible: false) - snapshotCache[test.initialMigrationKey] = .success(dbQueue) + snapshotCache[test.initialMigrationIdentifier] = .success(dbQueue) return .success(dbQueue) } catch { - snapshotCache[test.initialMigrationKey] = .failure(error) + snapshotCache[test.initialMigrationIdentifier] = .failure(error) return .failure(error) } }() @@ -175,7 +163,7 @@ class DatabaseSpec: QuickSpec { switch initialStateResult { case .success(let db): sourceDb = db case .failure(let error): - fail("Failed to prepare the initial state for '\(test.initialMigrationKey)'. Error: \(error)") + fail("Failed to prepare the initial state for '\(test.initialMigrationIdentifier)'. Error: \(error)") return } @@ -186,7 +174,7 @@ class DatabaseSpec: QuickSpec { // Peform the target migrations to ensure the migrations themselves worked correctly mockStorage.perform( - sortedMigrations: test.migrationsToTest, + migrations: test.migrationsToTest, async: false, onProgressUpdate: nil, onComplete: { result in finalResult = result } @@ -195,12 +183,68 @@ class DatabaseSpec: QuickSpec { switch finalResult { case .success: break case .failure(let error): - fail("Failed to migrate from '\(test.initialMigrationKey)' to '\(test.finalMigrationKey)'. Error: \(error)") + fail("Failed to migrate from '\(test.initialMigrationIdentifier)' to '\(test.finalMigrationIdentifier)'. Error: \(error)") case .none: - fail("Failed to migrate from '\(test.initialMigrationKey)' to '\(test.finalMigrationKey)'. Error: No result") + fail("Failed to migrate from '\(test.initialMigrationIdentifier)' to '\(test.finalMigrationIdentifier)'. Error: No result") } } } + + // MARK: -- migration order hasn't changed + it("migration order hasn't changed") { + expect(SNMessagingKit.migrations.map { $0.identifier }).to(equal([ + "utilitiesKit.initialSetup", + "utilitiesKit.SetupStandardJobs", + "utilitiesKit.YDBToGRDBMigration", + "snodeKit.initialSetup", + "snodeKit.SetupStandardJobs", + "messagingKit.initialSetup", + "messagingKit.SetupStandardJobs", + "snodeKit.YDBToGRDBMigration", + "messagingKit.YDBToGRDBMigration", + "snodeKit.FlagMessageHashAsDeletedOrInvalid", + "messagingKit.RemoveLegacyYDB", + "utilitiesKit.AddJobPriority", + "messagingKit.FixDeletedMessageReadState", + "messagingKit.FixHiddenModAdminSupport", + "messagingKit.HomeQueryOptimisationIndexes", + "uiKit.ThemePreferences", + "messagingKit.EmojiReacts", + "messagingKit.OpenGroupPermission", + "messagingKit.AddThreadIdToFTS", + "utilitiesKit.AddJobUniqueHash", + "snodeKit.AddSnodeReveivedMessageInfoPrimaryKey", + "snodeKit.DropSnodeCache", + "snodeKit.SplitSnodeReceivedMessageInfo", + "snodeKit.ResetUserConfigLastHashes", + "messagingKit.AddPendingReadReceipts", + "messagingKit.AddFTSIfNeeded", + "messagingKit.SessionUtilChanges", + "messagingKit.GenerateInitialUserConfigDumps", + "messagingKit.BlockCommunityMessageRequests", + "messagingKit.MakeBrokenProfileTimestampsNullable", + "messagingKit.RebuildFTSIfNeeded_2_4_5", + "messagingKit.DisappearingMessagesWithTypes", + "messagingKit.ScheduleAppUpdateCheckJob", + "messagingKit.AddMissingWhisperFlag", + "messagingKit.ReworkRecipientState", + "messagingKit.GroupsRebuildChanges", + "messagingKit.GroupsExpiredFlag", + "messagingKit.FixBustedInteractionVariant", + "messagingKit.DropLegacyClosedGroupKeyPairTable", + "messagingKit.MessageDeduplicationTable", + "utilitiesKit.RenameTableSettingToKeyValueStore", + "messagingKit.MoveSettingsToLibSession", + "messagingKit.RenameAttachments", + "messagingKit.AddProMessageFlag" + ])) + } + + // MARK: -- there are no duplicate migration names + it("there are no duplicate migration names") { + expect(Set(SNMessagingKit.migrations.map { $0.identifier }).sorted()) + .to(equal(SNMessagingKit.migrations.map { $0.identifier }.sorted())) + } } } } @@ -236,15 +280,15 @@ private struct TableColumn: Hashable { private class MigrationTest { static var explicitValues: [TableColumn: (any DatabaseValueConvertible)] = [:] - let initialMigrations: [Storage.KeyedMigration] - let migrationsToTest: [Storage.KeyedMigration] + let initialMigrations: [Migration.Type] + let migrationsToTest: [Migration.Type] - var initialMigrationKey: String { return (initialMigrations.last?.key ?? "an empty database") } - var finalMigrationKey: String { return (migrationsToTest.last?.key ?? "invalid") } + var initialMigrationIdentifier: String { return (initialMigrations.last?.identifier ?? "an empty database") } + var finalMigrationIdentifier: String { return (migrationsToTest.last?.identifier ?? "invalid") } private init( - initialMigrations: [Storage.KeyedMigration], - migrationsToTest: [Storage.KeyedMigration] + initialMigrations: [Migration.Type], + migrationsToTest: [Migration.Type] ) { self.initialMigrations = initialMigrations self.migrationsToTest = migrationsToTest @@ -252,7 +296,7 @@ private class MigrationTest { // MARK: - Test Data - static func extractTests(_ allMigrations: [Storage.KeyedMigration]) -> [MigrationTest] { + static func extractTests(_ allMigrations: [Migration.Type]) -> [MigrationTest] { return (0..<(allMigrations.count - 1)) .flatMap { index -> [MigrationTest] in ((index + 1).. MigrationTest in @@ -264,10 +308,10 @@ private class MigrationTest { } } - static func extractDatabaseTypes(_ allMigrations: [Storage.KeyedMigration]) -> [(TableRecord & FetchableRecord).Type] { + static func extractDatabaseTypes(_ allMigrations: [Migration.Type]) -> [(TableRecord & FetchableRecord).Type] { return Array(allMigrations .reduce(into: [:]) { result, next in - next.migration.createdTables.forEach { table in + next.createdTables.forEach { table in result[ObjectIdentifier(table).hashValue] = table } } @@ -392,69 +436,3 @@ private class MigrationTest { } } } - -enum TestAllMigrationRequirementsReversedMigratableTarget: MigratableTarget { // Just to make the external API nice - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .session, - migrations: [ - [ - TestRequiresAllMigrationRequirementsReversedMigration.self - ] - ] - ) - } -} - -enum TestRequiresLibSessionStateMigratableTarget: MigratableTarget { // Just to make the external API nice - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .session, - migrations: [ - [ - TestRequiresLibSessionStateMigration.self - ] - ] - ) - } -} - -enum TestRequiresSessionIdCachedMigratableTarget: MigratableTarget { // Just to make the external API nice - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .session, - migrations: [ - [ - TestRequiresSessionIdCachedMigration.self - ] - ] - ) - } -} - -enum TestRequiresAllMigrationRequirementsReversedMigration: Migration { - static let target: TargetMigrations.Identifier = .session - static let identifier: String = "test" // stringlint:ignore - static let minExpectedRunDuration: TimeInterval = 0.1 - static let createdTables: [(TableRecord & FetchableRecord).Type] = [] - - static func migrate(_ db: ObservingDatabase, using dependencies: Dependencies) throws {} -} - -enum TestRequiresLibSessionStateMigration: Migration { - static let target: TargetMigrations.Identifier = .session - static let identifier: String = "test" // stringlint:ignore - static let minExpectedRunDuration: TimeInterval = 0.1 - static let createdTables: [(TableRecord & FetchableRecord).Type] = [] - - static func migrate(_ db: ObservingDatabase, using dependencies: Dependencies) throws {} -} - -enum TestRequiresSessionIdCachedMigration: Migration { - static let target: TargetMigrations.Identifier = .session - static let identifier: String = "test" // stringlint:ignore - static let minExpectedRunDuration: TimeInterval = 0.1 - static let createdTables: [(TableRecord & FetchableRecord).Type] = [] - - static func migrate(_ db: ObservingDatabase, using dependencies: Dependencies) throws {} -} diff --git a/SessionTests/Onboarding/OnboardingSpec.swift b/SessionTests/Onboarding/OnboardingSpec.swift index a9e37a2154..f060ee7e98 100644 --- a/SessionTests/Onboarding/OnboardingSpec.swift +++ b/SessionTests/Onboarding/OnboardingSpec.swift @@ -9,7 +9,7 @@ import SessionUIKit import SessionUtilitiesKit @testable import Session -@testable import SessionSnodeKit +@testable import SessionNetworkingKit @testable import SessionMessagingKit class OnboardingSpec: AsyncSpec { @@ -24,12 +24,7 @@ class OnboardingSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: SNMessagingKit.migrations, using: dependencies ) @TestState(singleton: .crypto, in: dependencies) var mockCrypto: MockCrypto! = MockCrypto( @@ -126,7 +121,7 @@ class OnboardingSpec: AsyncSpec { .thenReturn(MockNetwork.batchResponseData( with: [ ( - SnodeAPI.Endpoint.getMessages, + Network.SnodeAPI.Endpoint.getMessages, GetMessagesResponse( messages: (pendingPushes? .pushData diff --git a/SessionTests/Session.xctestplan b/SessionTests/Session.xctestplan index fcfc4d45ad..a9c2927686 100644 --- a/SessionTests/Session.xctestplan +++ b/SessionTests/Session.xctestplan @@ -34,7 +34,7 @@ { "containerPath" : "container:Session.xcodeproj", "identifier" : "C3C2A59E255385C100C340D1", - "name" : "SessionSnodeKit" + "name" : "SessionNetworkingKit" }, { "containerPath" : "container:Session.xcodeproj", @@ -87,7 +87,7 @@ "target" : { "containerPath" : "container:Session.xcodeproj", "identifier" : "FDB5DAF92A981C42002C8721", - "name" : "SessionSnodeKitTests" + "name" : "SessionNetworkingKitTests" } }, { diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index ceda704ea2..2a55c6ffae 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -6,7 +6,7 @@ import Quick import Nimble import SessionUtil import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit @@ -21,12 +21,7 @@ class NotificationContentViewModelSpec: AsyncSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self, - DeprecatedUIKitMigrationTarget.self - ], + migrations: SNMessagingKit.migrations, using: dependencies ) @TestState var secretKey: [UInt8]! = Array(Data(hex: TestConstants.edSecretKey)) diff --git a/SessionUIKit/Components/Separator.swift b/SessionUIKit/Components/Separator.swift index d35add8fda..3230665d81 100644 --- a/SessionUIKit/Components/Separator.swift +++ b/SessionUIKit/Components/Separator.swift @@ -68,17 +68,20 @@ public final class Separator: UIView { addSubview(rightLine) addSubview(titleLabel) - titleLabel.center(.horizontal, in: self) - titleLabel.center(.vertical, in: self) - roundedLine.pin(.top, to: .top, of: self) - roundedLine.pin(.top, to: .top, of: titleLabel, withInset: -6) - roundedLine.pin(.leading, to: .leading, of: titleLabel, withInset: -10) - roundedLine.pin(.trailing, to: .trailing, of: titleLabel, withInset: 10) - roundedLine.pin(.bottom, to: .bottom, of: titleLabel, withInset: 6) - roundedLine.pin(.bottom, to: .bottom, of: self) + titleLabel.pin(.top, to: .top, of: roundedLine, withInset: 6) + titleLabel.pin(.leading, to: .leading, of: roundedLine, withInset: 10) + titleLabel.pin(.trailing, to: .trailing, of: roundedLine, withInset: -10) + titleLabel.pin(.bottom, to: .bottom, of: roundedLine, withInset: -6) + + roundedLine.center(.horizontal, in: self) + roundedLine.center(.vertical, in: self) + roundedLine.setContentHugging(.horizontal, to: .required) + roundedLine.setCompressionResistance(.horizontal, to: .required) + leftLine.pin(.leading, to: .leading, of: self) leftLine.pin(.trailing, to: .leading, of: roundedLine) leftLine.center(.vertical, in: self) + rightLine.pin(.leading, to: .trailing, of: roundedLine) rightLine.pin(.trailing, to: .trailing, of: self) rightLine.center(.vertical, in: self) diff --git a/SessionUIKit/Components/SessionImageView.swift b/SessionUIKit/Components/SessionImageView.swift index 0eec4aab2b..a768725213 100644 --- a/SessionUIKit/Components/SessionImageView.swift +++ b/SessionUIKit/Components/SessionImageView.swift @@ -138,7 +138,7 @@ public class SessionImageView: UIImageView { } @MainActor - public func loadImage(_ source: ImageDataManager.DataSource, onComplete: ((ImageDataManager.ProcessedImageData?) -> Void)? = nil) { + public func loadImage(_ source: ImageDataManager.DataSource, onComplete: (@MainActor (ImageDataManager.ProcessedImageData?) -> Void)? = nil) { /// If we are trying to load the image that is already displayed then no need to do anything if currentLoadIdentifier == source.identifier && (self.image == nil || isAnimating()) { /// If it was an animation that got paused then resume it diff --git a/SessionUIKit/Style Guide/ThemeManager.swift b/SessionUIKit/Style Guide/ThemeManager.swift index 1ceae3a4dc..e2dff643f2 100644 --- a/SessionUIKit/Style Guide/ThemeManager.swift +++ b/SessionUIKit/Style Guide/ThemeManager.swift @@ -53,10 +53,6 @@ public enum ThemeManager { _primaryColor = targetPrimaryColor _hasLoadedTheme = true - if !hasSetInitialSystemTrait || themeChanged { - updateAllUI() - } - if matchSystemChanged { _matchSystemNightModeSetting = targetMatchSystemNightModeSetting @@ -65,8 +61,19 @@ public enum ThemeManager { SNUIKit.mainWindow?.overrideUserInterfaceStyle = .unspecified } - // If the theme was changed then trigger the callback for the theme settings change (so it gets persisted) - guard themeChanged || matchSystemChanged else { return } + // If the theme was changed then trigger a UI update and the callback for the theme settings + // change (so it gets persisted) + guard themeChanged || matchSystemChanged else { + if !hasSetInitialSystemTrait { + updateAllUI() + } + + return + } + + if !hasSetInitialSystemTrait || themeChanged { + updateAllUI() + } SNUIKit.themeSettingsChanged(targetTheme, targetPrimaryColor, targetMatchSystemNightModeSetting) } @@ -82,10 +89,10 @@ public enum ThemeManager { // Swap to the appropriate light/dark mode switch (currentUserInterfaceStyle, ThemeManager.currentTheme) { - case (.light, .classicDark): updateThemeState(theme: .classicLight) - case (.light, .oceanDark): updateThemeState(theme: .oceanLight) - case (.dark, .classicLight): updateThemeState(theme: .classicDark) - case (.dark, .oceanLight): updateThemeState(theme: .oceanDark) + case (.light, .classicDark): updateThemeState(theme: .classicLight, primaryColor: _primaryColor) + case (.light, .oceanDark): updateThemeState(theme: .oceanLight, primaryColor: _primaryColor) + case (.dark, .classicLight): updateThemeState(theme: .classicDark, primaryColor: _primaryColor) + case (.dark, .oceanLight): updateThemeState(theme: .oceanDark, primaryColor: _primaryColor) default: break } } diff --git a/SessionUIKit/Style Guide/Values.swift b/SessionUIKit/Style Guide/Values.swift index ffd825c2af..d6a6894e9f 100644 --- a/SessionUIKit/Style Guide/Values.swift +++ b/SessionUIKit/Style Guide/Values.swift @@ -8,7 +8,7 @@ public enum Values { public static let veryLowOpacity = CGFloat(0.12) public static let lowOpacity = CGFloat(0.4) public static let mediumOpacity = CGFloat(0.6) - public static let highOpacity = CGFloat(0.75) + public static let highOpacity = CGFloat(0.7) // MARK: - Font Sizes public static let miniFontSize = isIPhone5OrSmaller ? CGFloat(8) : CGFloat(10) @@ -31,6 +31,8 @@ public enum Values { public static let accentLineThickness = CGFloat(4) public static let searchBarHeight = CGFloat(36) + + public static let gradientPaletteWidth = CGFloat(12) public static var separatorThickness: CGFloat { return 1 / UIScreen.main.scale } diff --git a/SessionUIKit/Types/ImageDataManager.swift b/SessionUIKit/Types/ImageDataManager.swift index 4cfaa8083f..9e0dd29674 100644 --- a/SessionUIKit/Types/ImageDataManager.swift +++ b/SessionUIKit/Types/ImageDataManager.swift @@ -59,9 +59,10 @@ public actor ImageDataManager: ImageDataManagerType { return processedData } - nonisolated public func load( + @MainActor + public func load( _ source: ImageDataManager.DataSource, - onComplete: @escaping (ImageDataManager.ProcessedImageData?) -> Void + onComplete: @MainActor @escaping (ImageDataManager.ProcessedImageData?) -> Void ) { Task { [weak self] in let result: ImageDataManager.ProcessedImageData? = await self?.load(source) @@ -863,9 +864,11 @@ public extension ImageDataManager { public protocol ImageDataManagerType { @discardableResult func load(_ source: ImageDataManager.DataSource) async -> ImageDataManager.ProcessedImageData? - nonisolated func load( + + @MainActor + func load( _ source: ImageDataManager.DataSource, - onComplete: @escaping (ImageDataManager.ProcessedImageData?) -> Void + onComplete: @MainActor @escaping (ImageDataManager.ProcessedImageData?) -> Void ) func cachedImage(identifier: String) async -> ImageDataManager.ProcessedImageData? diff --git a/SessionUIKit/Utilities/Localization+Style.swift b/SessionUIKit/Utilities/Localization+Style.swift index 788e777c10..43dce7671e 100644 --- a/SessionUIKit/Utilities/Localization+Style.swift +++ b/SessionUIKit/Utilities/Localization+Style.swift @@ -20,6 +20,9 @@ public extension ThemedAttributedString { case strikethrough = "s" case primaryTheme = "span" case icon = "icon" + case warningTheme = "warn" + case dangerTheme = "error" + case disabledTheme = "disabled" // MARK: - Functions @@ -53,6 +56,9 @@ public extension ThemedAttributedString { case .strikethrough: return [.strikethroughStyle: NSUnderlineStyle.single.rawValue] case .primaryTheme: return [.themeForegroundColor: ThemeValue.sessionButton_text] case .icon: return Lucide.attributes(for: font) + case .warningTheme: return [.themeForegroundColor: ThemeValue.warning] + case .dangerTheme: return [.themeForegroundColor: ThemeValue.danger] + case .disabledTheme: return [.themeForegroundColor: ThemeValue.disabled] } } } @@ -184,6 +190,9 @@ private extension Collection where Element == ThemedAttributedString.HTMLTag { case .icon: result[.font] = fontWith(Lucide.font(ofSize: (font.pointSize + 1)), traits: []) result[.baselineOffset] = Lucide.defaultBaselineOffset + case .warningTheme: result[.themeForegroundColor] = ThemeValue.warning + case .dangerTheme: result[.themeForegroundColor] = ThemeValue.danger + case .disabledTheme: result[.themeForegroundColor] = ThemeValue.disabled } } } diff --git a/SessionUIKit/Utilities/UIView+Constraints.swift b/SessionUIKit/Utilities/UIView+Constraints.swift index 0570ed3325..6d905e234a 100644 --- a/SessionUIKit/Utilities/UIView+Constraints.swift +++ b/SessionUIKit/Utilities/UIView+Constraints.swift @@ -211,16 +211,22 @@ public extension UIView { } } - func pin(to view: UIView) { - [ HorizontalEdge.leading, HorizontalEdge.trailing ].forEach { pin($0, to: $0, of: view) } - [ VerticalEdge.top, VerticalEdge.bottom ].forEach { pin($0, to: $0, of: view) } + @discardableResult + func pin(to view: UIView) -> [NSLayoutConstraint] { + return [ + [ HorizontalEdge.leading, HorizontalEdge.trailing ].map { pin($0, to: $0, of: view) }, + [ VerticalEdge.top, VerticalEdge.bottom ].map { pin($0, to: $0, of: view) } + ].flatMap { $0 } } - func pin(to view: UIView, withInset inset: CGFloat) { - pin(.leading, to: .leading, of: view, withInset: inset) - pin(.top, to: .top, of: view, withInset: inset) - view.pin(.trailing, to: .trailing, of: self, withInset: inset) - view.pin(.bottom, to: .bottom, of: self, withInset: inset) + @discardableResult + func pin(to view: UIView, withInset inset: CGFloat) -> [NSLayoutConstraint] { + return [ + pin(.leading, to: .leading, of: view, withInset: inset), + pin(.top, to: .top, of: view, withInset: inset), + view.pin(.trailing, to: .trailing, of: self, withInset: inset), + view.pin(.bottom, to: .bottom, of: self, withInset: inset) + ] } @discardableResult diff --git a/SessionUtilitiesKit/Configuration.swift b/SessionUtilitiesKit/Configuration.swift index 16485d4f3f..445a3594ef 100644 --- a/SessionUtilitiesKit/Configuration.swift +++ b/SessionUtilitiesKit/Configuration.swift @@ -1,45 +1,15 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation -import UIKit.UIFont -import GRDB -public enum SNUtilitiesKit: MigratableTarget { // Just to make the external API nice +public enum SNUtilitiesKit { public private(set) static var maxFileSize: UInt = 0 public private(set) static var maxValidImageDimension: Int = 0 + public static var isRunningTests: Bool { ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil // stringlint:ignore } - public static func migrations() -> TargetMigrations { - return TargetMigrations( - identifier: .utilitiesKit, - migrations: [ - [ - // Intentionally including the '_003_YDBToGRDBMigration' in the first migration - // set to ensure the 'Identity' data is migrated before any other migrations are - // run (some need access to the users publicKey) - _001_InitialSetupMigration.self, - _002_SetupStandardJobs.self, - _003_YDBToGRDBMigration.self - ], // Initial DB Creation - [], // YDB to GRDB Migration - [], // Legacy DB removal - [ - _004_AddJobPriority.self - ], // Add job priorities - [], // Fix thread FTS - [ - _005_AddJobUniqueHash.self - ], - [ - _006_RenameTableSettingToKeyValueStore.self - ], // Renamed `Setting` to `KeyValueStore` - [] - ] - ) - } - public static func configure( networkMaxFileSize: UInt, maxValidImageDimention: Int, diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index b162e27c99..09817afb74 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -246,8 +246,6 @@ open class Storage { // MARK: - Migrations - public typealias KeyedMigration = (key: String, identifier: TargetMigrations.Identifier, migration: Migration.Type) - public static func appliedMigrationIdentifiers(_ db: ObservingDatabase) -> Set { let migrator: DatabaseMigrator = DatabaseMigrator() @@ -255,47 +253,11 @@ open class Storage { .defaulting(to: []) } - public static func sortedMigrationInfo(migrationTargets: [MigratableTarget.Type]) -> [KeyedMigration] { - typealias MigrationInfo = (identifier: TargetMigrations.Identifier, migrations: TargetMigrations.MigrationSet) - - return migrationTargets - .map { target -> TargetMigrations in target.migrations() } - .sorted() - .reduce(into: [[MigrationInfo]]()) { result, next in - next.migrations.enumerated().forEach { index, migrationSet in - if result.count <= index { - result.append([]) - } - - result[index] = (result[index] + [(next.identifier, migrationSet)]) - } - } - .reduce(into: []) { result, next in - next.forEach { identifier, migrations in - result.append(contentsOf: migrations.map { (identifier.key(with: $0), identifier, $0) }) - } - } - } - public func perform( - migrationTargets: [MigratableTarget.Type], + migrations: [Migration.Type], async: Bool = true, onProgressUpdate: ((CGFloat, TimeInterval) -> ())?, onComplete: @escaping (Result) -> () - ) { - perform( - sortedMigrations: Storage.sortedMigrationInfo(migrationTargets: migrationTargets), - async: async, - onProgressUpdate: onProgressUpdate, - onComplete: onComplete - ) - } - - internal func perform( - sortedMigrations: [KeyedMigration], - async: Bool, - onProgressUpdate: ((CGFloat, TimeInterval) -> ())?, - onComplete: @escaping (Result) -> () ) { guard isValid, let dbWriter: DatabaseWriter = dbWriter else { let error: Error = (startupError ?? StorageError.startupFailed) @@ -306,36 +268,33 @@ open class Storage { // Setup and run any required migrations var migrator: DatabaseMigrator = DatabaseMigrator() - sortedMigrations.forEach { _, identifier, migration in - migrator.registerMigration( - self, - targetIdentifier: identifier, - migration: migration, - using: dependencies - ) + migrations.forEach { migration in + migrator.registerMigration(migration.identifier) { [dependencies] db in + let migration = migration.loggedMigrate(using: dependencies) + try migration(ObservingDatabase.create(db, using: dependencies)) + } } // Determine which migrations need to be performed and gather the relevant settings needed to // inform the app of progress/states let completedMigrations: [String] = (try? dbWriter.read { db in try migrator.completedMigrations(db) }) .defaulting(to: []) - let unperformedMigrations: [KeyedMigration] = sortedMigrations + let unperformedMigrations: [Migration.Type] = migrations .reduce(into: []) { result, next in - guard !completedMigrations.contains(next.key) else { return } + guard !completedMigrations.contains(next.identifier) else { return } result.append(next) } let migrationToDurationMap: [String: TimeInterval] = unperformedMigrations .reduce(into: [:]) { result, next in - result[next.key] = next.migration.minExpectedRunDuration + result[next.identifier] = next.minExpectedRunDuration } - let unperformedMigrationDurations: [TimeInterval] = unperformedMigrations - .map { _, _, migration in migration.minExpectedRunDuration } + let unperformedMigrationDurations: [TimeInterval] = unperformedMigrations.map { $0.minExpectedRunDuration } let totalMinExpectedDuration: TimeInterval = migrationToDurationMap.values.reduce(0, +) // Store the logic to handle migration progress and completion let progressUpdater: (String, CGFloat) -> Void = { (targetKey: String, progress: CGFloat) in - guard let migrationIndex: Int = unperformedMigrations.firstIndex(where: { key, _, _ in key == targetKey }) else { + guard let migrationIndex: Int = unperformedMigrations.firstIndex(where: { $0.identifier == targetKey }) else { return } @@ -352,8 +311,8 @@ open class Storage { let migrationCompleted: (Result) -> () = { [weak self, migrator, dbWriter, dependencies] result in // Make sure to transition the progress updater to 100% for the final migration (just // in case the migration itself didn't update to 100% itself) - if let lastMigrationKey: String = unperformedMigrations.last?.key { - MigrationExecution.current?.progressUpdater(lastMigrationKey, 1) + if let lastMigrationIdentifier: String = unperformedMigrations.last?.identifier { + MigrationExecution.current?.progressUpdater(lastMigrationIdentifier, 1) } self?.hasCompletedMigrations = true @@ -401,8 +360,8 @@ open class Storage { let migrationContext: MigrationExecution.Context = MigrationExecution.Context(progressUpdater: progressUpdater) // If we have an unperformed migration then trigger the progress updater immediately - if let firstMigrationKey: String = unperformedMigrations.first?.key { - migrationContext.progressUpdater(firstMigrationKey, 0) + if let firstMigrationIdentifier: String = unperformedMigrations.first?.identifier { + migrationContext.progressUpdater(firstMigrationIdentifier, 0) } MigrationExecution.$current.withValue(migrationContext) { @@ -494,6 +453,8 @@ open class Storage { .defaulting(to: "N/A") Log.verbose(.storage, "Database suspended successfully for \(id) (db: \(dbFileSize), shm: \(dbShmFileSize), wal: \(dbWalFileSize)).") } + + dependencies.notifyAsync(key: .databaseLifecycle(.suspended)) } /// This method reverses the database suspension used to prevent the `0xdead10cc` exception (see `suspendDatabaseAccess()` @@ -503,6 +464,7 @@ open class Storage { isSuspended = false Log.info(.storage, "Database access resumed.") + dependencies.notifyAsync(key: .databaseLifecycle(.resumed)) } public func checkpoint(_ mode: Database.CheckpointMode) throws { diff --git a/SessionUtilitiesKit/Database/Types/Migration.swift b/SessionUtilitiesKit/Database/Types/Migration.swift index 36a9026a8f..4609edfbdd 100644 --- a/SessionUtilitiesKit/Database/Types/Migration.swift +++ b/SessionUtilitiesKit/Database/Types/Migration.swift @@ -12,7 +12,6 @@ public extension Log.Category { // MARK: - Migration public protocol Migration { - static var target: TargetMigrations.Identifier { get } static var identifier: String { get } static var minExpectedRunDuration: TimeInterval { get } static var createdTables: [(TableRecord & FetchableRecord).Type] { get } @@ -21,17 +20,12 @@ public protocol Migration { } public extension Migration { - static func loggedMigrate( - _ storage: Storage?, - targetIdentifier: TargetMigrations.Identifier, - using dependencies: Dependencies - ) -> ((_ db: ObservingDatabase) throws -> ()) { + static func loggedMigrate(using dependencies: Dependencies) -> ((_ db: ObservingDatabase) throws -> ()) { return { (db: ObservingDatabase) in - Log.info(.migration, "Starting \(targetIdentifier.key(with: self))") + Log.info(.migration, "Starting \(identifier)") /// Store the `currentlyRunningMigration` in case it's useful MigrationExecution.current?.currentlyRunningMigration = MigrationExecution.CurrentlyRunningMigration( - identifier: targetIdentifier, migration: self ) defer { MigrationExecution.current?.currentlyRunningMigration = nil } @@ -44,7 +38,7 @@ public extension Migration { MigrationExecution.current?.observedEvents.append(contentsOf: db.events) MigrationExecution.current?.postCommitActions.merge(db.postCommitActions) { old, _ in old } - Log.info(.migration, "Completed \(targetIdentifier.key(with: self))") + Log.info(.migration, "Completed \(identifier)") } } } @@ -53,10 +47,9 @@ public extension Migration { public enum MigrationExecution { public struct CurrentlyRunningMigration: ThreadSafeType { - public let identifier: TargetMigrations.Identifier public let migration: Migration.Type - public var key: String { identifier.key(with: migration) } + public var key: String { migration.identifier } } public final class Context { @@ -83,6 +76,7 @@ public enum MigrationExecution { @TaskLocal public static var current: Context? + // stringlint:ignore_contents public static func updateProgress(_ progress: CGFloat) { // In test builds ignore any migration progress updates (we run in a custom database writer anyway) guard !SNUtilitiesKit.isRunningTests else { return } diff --git a/SessionUtilitiesKit/Database/Types/TargetMigrations.swift b/SessionUtilitiesKit/Database/Types/TargetMigrations.swift deleted file mode 100644 index b320516d5c..0000000000 --- a/SessionUtilitiesKit/Database/Types/TargetMigrations.swift +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import GRDB - -public protocol MigratableTarget { - static func migrations() -> TargetMigrations -} - -public struct TargetMigrations: Comparable { - /// This identifier is used to determine the order each set of migrations should run in. - /// - /// All migrations within a specific set will run first, followed by all migrations for the same set index in - /// the next `Identifier` before moving on to the next `MigrationSet`. So given the migrations: - /// - /// `{a: [1], [2, 3]}, {b: [4, 5], [6]}` - /// - /// the migrations will run in the following order: - /// - /// `a1, b4, b5, a2, a3, b6` - public enum Identifier: String, CaseIterable, Comparable { - // WARNING: The string version of these cases are used as migration identifiers so - // changing them will result in the migrations running again - case session - case utilitiesKit - case snodeKit - case messagingKit - case _deprecatedUIKit = "uiKit" - case test - - public static func < (lhs: Self, rhs: Self) -> Bool { - let lhsIndex: Int = (Identifier.allCases.firstIndex(of: lhs) ?? Identifier.allCases.count) - let rhsIndex: Int = (Identifier.allCases.firstIndex(of: rhs) ?? Identifier.allCases.count) - - return (lhsIndex < rhsIndex) - } - - public func key(with migration: Migration.Type) -> String { - return "\(self.rawValue).\(migration.identifier)" - } - } - - public typealias MigrationSet = [Migration.Type] - - let identifier: Identifier - let migrations: [MigrationSet] - - // MARK: - Initialization - - public init( - identifier: Identifier, - migrations: [MigrationSet] - ) { - guard !migrations.contains(where: { migration in migration.contains(where: { $0.target != identifier }) }) else { - preconditionFailure("Attempted to register a migration with the wrong target") - } - - self.identifier = identifier - self.migrations = migrations - } - - // MARK: - Equatable - - public static func == (lhs: TargetMigrations, rhs: TargetMigrations) -> Bool { - return ( - lhs.identifier == rhs.identifier && - lhs.migrations.count == rhs.migrations.count - ) - } - - // MARK: - Comparable - - public static func < (lhs: Self, rhs: Self) -> Bool { - return (lhs.identifier < rhs.identifier) - } -} diff --git a/SessionUtilitiesKit/Database/Utilities/DatabaseMigrator+Utilities.swift b/SessionUtilitiesKit/Database/Utilities/DatabaseMigrator+Utilities.swift deleted file mode 100644 index 748175ca8d..0000000000 --- a/SessionUtilitiesKit/Database/Utilities/DatabaseMigrator+Utilities.swift +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import GRDB - -public extension DatabaseMigrator { - mutating func registerMigration( - _ storage: Storage?, - targetIdentifier: TargetMigrations.Identifier, - migration: Migration.Type, - foreignKeyChecks: ForeignKeyChecks = .deferred, - using dependencies: Dependencies - ) { - self.registerMigration( - targetIdentifier.key(with: migration), - migrate: { db in - let migration = migration.loggedMigrate(storage, targetIdentifier: targetIdentifier, using: dependencies) - try migration(ObservingDatabase.create(db, using: dependencies)) - } - ) - } -} diff --git a/SessionUtilitiesKit/General/Logging.swift b/SessionUtilitiesKit/General/Logging.swift index 8f722931ea..67b529d8ec 100644 --- a/SessionUtilitiesKit/General/Logging.swift +++ b/SessionUtilitiesKit/General/Logging.swift @@ -22,6 +22,14 @@ public extension FeatureStorage { ) } + static func logLevel(group: Log.Group) -> FeatureConfig { + return Dependencies.create( + identifier: "\(Log.Group.identifierPrefix)\(group.name)", + groupIdentifier: "logging", + defaultOption: group.defaultLevel + ) + } + static let allLogLevels: FeatureConfig = Dependencies.create( identifier: "allLogLevels", groupIdentifier: "logging" @@ -50,33 +58,66 @@ public enum Log { case off case `default` + + var label: String { + switch self { + case .off: return "off" + case .verbose: return "verbose" + case .debug: return "debug" + case .info: return "info" + case .warn: return "warn" + case .error: return "error" + case .critical: return "critical" + case .default: return "default" + } + } + } + + public struct Group: Hashable { + public let name: String + public let defaultLevel: Log.Level + + fileprivate static let identifierPrefix: String = "group:" + + private init(name: String, defaultLevel: Log.Level) { + self.name = name + + switch AllLoggingCategories.existingGroup(for: name) { + case .some(let existingGroup): self.defaultLevel = existingGroup.defaultLevel + case .none: + self.defaultLevel = defaultLevel + AllLoggingCategories.register(group: self) + } + } + + @discardableResult public static func create( + _ group: String, + defaultLevel: Log.Level + ) -> Log.Group { + return Log.Group(name: group, defaultLevel: defaultLevel) + } } public struct Category: Hashable { public let rawValue: String - fileprivate let customPrefix: String + fileprivate let group: Group? fileprivate let customSuffix: String public let defaultLevel: Log.Level fileprivate static let identifierPrefix: String = "logLevel-" fileprivate var identifier: String { "\(Category.identifierPrefix)\(rawValue)" } - private init(rawValue: String, customPrefix: String, customSuffix: String, defaultLevel: Log.Level) { + private init(rawValue: String, group: Group?, customSuffix: String, defaultLevel: Log.Level) { + self.rawValue = rawValue + self.group = group + self.customSuffix = customSuffix + /// If we've already registered this category then assume the original has the correct `defaultLevel` and only /// modify the `customPrefix` value switch AllLoggingCategories.existingCategory(for: rawValue) { - case .some(let existingCategory): - self.rawValue = existingCategory.rawValue - self.customPrefix = customPrefix - self.customSuffix = customSuffix - self.defaultLevel = existingCategory.defaultLevel - + case .some(let existingCategory): self.defaultLevel = existingCategory.defaultLevel case .none: - self.rawValue = rawValue - self.customPrefix = customPrefix - self.customSuffix = customSuffix self.defaultLevel = defaultLevel - AllLoggingCategories.register(category: self) } } @@ -86,25 +127,25 @@ public enum Log { self.init( rawValue: identifier.substring(from: Category.identifierPrefix.count), - customPrefix: "", + group: nil, customSuffix: "", defaultLevel: .default ) } - public init(rawValue: String, customPrefix: String = "", customSuffix: String = "") { - self.init(rawValue: rawValue, customPrefix: customPrefix, customSuffix: customSuffix, defaultLevel: .default) + public init(rawValue: String, group: Group? = nil, customSuffix: String = "") { + self.init(rawValue: rawValue, group: group, customSuffix: customSuffix, defaultLevel: .default) } @discardableResult public static func create( _ rawValue: String, - customPrefix: String = "", + group: Group? = nil, customSuffix: String = "", defaultLevel: Log.Level ) -> Log.Category { return Log.Category( rawValue: rawValue, - customPrefix: customPrefix, + group: group, customSuffix: customSuffix, defaultLevel: defaultLevel ) @@ -653,12 +694,15 @@ public actor Logger: LoggerType { let defaultLogLevel: Log.Level = dependencies[feature: .logLevel(cat: .default)] let lowestCatLevel: Log.Level = categories .reduce(into: [], { result, next in - guard dependencies[feature: .logLevel(cat: next)] != .default else { - result.append(defaultLogLevel) - return - } + let explicitLevel: Log.Level = dependencies[feature: .logLevel(cat: next)] + let groupLevel: Log.Level? = next.group.map { dependencies[feature: .logLevel(group: $0)] } - result.append(dependencies[feature: .logLevel(cat: next)]) + switch (explicitLevel, groupLevel) { + case (.default, .none): result.append(defaultLogLevel) + case (.default, .default): result.append(defaultLogLevel) + case (_, .none): result.append(explicitLevel) + case (_, .some(let groupLevel)): result.append(min(explicitLevel, groupLevel)) + } }) .min() .defaulting(to: defaultLogLevel) @@ -678,16 +722,15 @@ public actor Logger: LoggerType { /// No point doubling up but we want to allow categories which match the `primaryPrefix` so that we /// have a mechanism for providing a different "default" log level for a specific target .filter { $0.rawValue != primaryPrefix } - .map { "\($0.customPrefix)\($0.rawValue)\($0.customSuffix)" } + .map { "\($0.group.map { "\($0.name):" } ?? "")\($0.rawValue)\($0.customSuffix)" } ) .joined(separator: ", ") - return "[\(prefixes)] " + return "[\(prefixes)]" }() /// Clean up the message if needed (replace double periods with single, trim whitespace, truncate pubkeys) - let logMessage: String = logPrefix - .appending(message) + let cleanedMessage: String = message .replacingOccurrences(of: "...", with: "|||") .replacingOccurrences(of: "..", with: ".") .replacingOccurrences(of: "|||", with: "...") @@ -709,16 +752,17 @@ public actor Logger: LoggerType { return updatedText } - + let ddLogMessage: String = "\(logPrefix) ".appending(cleanedMessage) + let consoleLogMessage: String = "\(logPrefix)[\(level)] ".appending(cleanedMessage) switch level { case .off, .default: return - case .verbose: DDLogVerbose("💙 \(logMessage)", file: file, function: function, line: line) - case .debug: DDLogDebug("💚 \(logMessage)", file: file, function: function, line: line) - case .info: DDLogInfo("💛 \(logMessage)", file: file, function: function, line: line) - case .warn: DDLogWarn("🧡 \(logMessage)", file: file, function: function, line: line) - case .error: DDLogError("❤️ \(logMessage)", file: file, function: function, line: line) - case .critical: DDLogError("🔥 \(logMessage)", file: file, function: function, line: line) + case .verbose: DDLogVerbose("💙 \(ddLogMessage)", file: file, function: function, line: line) + case .debug: DDLogDebug("💚 \(ddLogMessage)", file: file, function: function, line: line) + case .info: DDLogInfo("💛 \(ddLogMessage)", file: file, function: function, line: line) + case .warn: DDLogWarn("🧡 \(ddLogMessage)", file: file, function: function, line: line) + case .error: DDLogError("❤️ \(ddLogMessage)", file: file, function: function, line: line) + case .critical: DDLogError("🔥 \(ddLogMessage)", file: file, function: function, line: line) } let mainCategory: String = (categories.first?.rawValue ?? "General") @@ -730,7 +774,7 @@ public actor Logger: LoggerType { } #if DEBUG - systemLogger?.log(level, logMessage) + systemLogger?.log(level, consoleLogMessage) #endif } } @@ -858,6 +902,8 @@ extension Log.Level: FeatureOption { // MARK: - AllLoggingCategories public struct AllLoggingCategories: FeatureOption { + public static let allCases: [AllLoggingCategories] = [] + @ThreadSafeObject private static var registeredGroupDefaults: Set = [] @ThreadSafeObject private static var registeredCategoryDefaults: Set = [] // MARK: - Initialization @@ -865,7 +911,21 @@ public struct AllLoggingCategories: FeatureOption { public let rawValue: Int public init(rawValue: Int) { - self.rawValue = -1 // `0` is a protected value so can't use it + _ = Log.Category.default // Access the `default` log category to ensure it exists + self.rawValue = -1 // `0` is a protected value so can't use it + } + + fileprivate static func register(group: Log.Group) { + guard + !registeredGroupDefaults.contains(where: { existingGroup in + /// **Note:** We only want to use the `rawValue` to distinguish between logging categories + /// as the `defaultLevel` can change via the dev settings and any additional metadata could + /// be file/class specific + group.name == existingGroup.name + }) + else { return } + + _registeredGroupDefaults.performUpdate { $0.inserting(group) } } fileprivate static func register(category: Log.Category) { @@ -881,10 +941,21 @@ public struct AllLoggingCategories: FeatureOption { _registeredCategoryDefaults.performUpdate { $0.inserting(category) } } + fileprivate static func existingGroup(for name: String) -> Log.Group? { + return AllLoggingCategories.registeredGroupDefaults.first(where: { $0.name == name }) + } + fileprivate static func existingCategory(for cat: String) -> Log.Category? { return AllLoggingCategories.registeredCategoryDefaults.first(where: { $0.rawValue == cat }) } + public func currentValues(using dependencies: Dependencies) -> [Log.Group: Log.Level] { + return AllLoggingCategories.registeredGroupDefaults + .reduce(into: [:]) { result, group in + result[group] = dependencies[feature: .logLevel(group: group)] + } + } + public func currentValues(using dependencies: Dependencies) -> [Log.Category: Log.Level] { return AllLoggingCategories.registeredCategoryDefaults .reduce(into: [:]) { result, cat in diff --git a/SessionUtilitiesKit/LibSession/LibSession.swift b/SessionUtilitiesKit/LibSession/LibSession.swift index 916686f2f8..e787e7b4db 100644 --- a/SessionUtilitiesKit/LibSession/LibSession.swift +++ b/SessionUtilitiesKit/LibSession/LibSession.swift @@ -17,6 +17,10 @@ public extension Log.Category { static let libSession: Log.Category = .create("LibSession", defaultLevel: .info) } +public extension Log.Group { + static let libSession: Log.Group = .create("libSession", defaultLevel: .info) +} + // MARK: - Logging extension LibSession { @@ -29,7 +33,13 @@ extension LibSession { ObservationBuilder.observe(.featureGroup(.allLogLevels), using: dependencies) { [dependencies] _ in let currentLogLevels: [Log.Category: Log.Level] = dependencies[feature: .allLogLevels] .currentValues(using: dependencies) - let cDefaultLevel: LOG_LEVEL = (currentLogLevels[.default]?.libSession ?? LOG_LEVEL_OFF) + let currentGroupLogLevels: [Log.Group: Log.Level] = dependencies[feature: .allLogLevels] + .currentValues(using: dependencies) + let targetDefault: Log.Level? = min( + (currentLogLevels[.default] ?? .off), + (currentGroupLogLevels[.libSession] ?? .off) + ) + let cDefaultLevel: LOG_LEVEL = (targetDefault?.libSession ?? LOG_LEVEL_OFF) session_logger_set_level_default(cDefaultLevel) session_logger_reset_level(cDefaultLevel) @@ -57,20 +67,45 @@ extension LibSession { DispatchQueue.global(qos: .background).async { /// Logs from libSession come through in the format: /// `[yyyy-MM-dd hh:mm:ss] [+{lifetime}s] [{cat}:{lvl}|log.hpp:{line}] {message}` - /// We want to remove the extra data because it doesn't help the logs + /// + /// We want to simplify the message because our logging already includes category and timestamp information: + /// `[+{lifetime}s] {message}` let processedMessage: String = { - let logParts: [String] = msg.components(separatedBy: "] ") + let trimmedMsg = msg.trimmingCharacters(in: .whitespacesAndNewlines) - guard logParts.count == 4 else { return msg.trimmingCharacters(in: .whitespacesAndNewlines) } + guard + let timestampRegex: NSRegularExpression = LibSession.timestampRegex, + let messageStartRegex: NSRegularExpression = LibSession.messageStartRegex + else { return trimmedMsg } - let message: String = String(logParts[3]).trimmingCharacters(in: .whitespacesAndNewlines) + let fullRange = NSRange(trimmedMsg.startIndex.. ObservableKey { + ObservableKey("appLifecycle-\(event)", .appLifecycle) + } + + static func databaseLifecycle(_ event: DatabaseLifecycle) -> ObservableKey { + ObservableKey("databaseLifecycle-\(event)", .databaseLifecycle) + } + static func feature(_ key: FeatureConfig) -> ObservableKey { ObservableKey(key.identifier, .feature) } @@ -17,6 +25,26 @@ public extension ObservableKey { } public extension GenericObservableKey { + static let appLifecycle: GenericObservableKey = "appLifecycle" + static let databaseLifecycle: GenericObservableKey = "databaseLifecycle" static let feature: GenericObservableKey = "feature" static let featureGroup: GenericObservableKey = "featureGroup" } + +// MARK: - AppLifecycle + +public enum AppLifecycle: String, Sendable { + case didEnterBackground + case willEnterForeground + case didBecomeActive + case willResignActive + case didReceiveMemoryWarning + case willTerminate +} + +// MARK: - DatabaseLifecycle + +public enum DatabaseLifecycle: String, Sendable { + case suspended + case resumed +} diff --git a/SessionUtilitiesKit/Observations/ObservationManager.swift b/SessionUtilitiesKit/Observations/ObservationManager.swift index 4bff64a107..e0b8388818 100644 --- a/SessionUtilitiesKit/Observations/ObservationManager.swift +++ b/SessionUtilitiesKit/Observations/ObservationManager.swift @@ -1,22 +1,48 @@ // Copyright © 2025 Rangeproof Pty Ltd. All rights reserved. import Foundation +import UIKit.UIApplication // MARK: - Singleton public extension Singleton { static let observationManager: SingletonConfig = Dependencies.create( identifier: "observationManager", - createInstance: { dependencies in ObservationManager() } + createInstance: { dependencies in ObservationManager(using: dependencies) } ) } // MARK: - ObservationManager public actor ObservationManager { + private let lifecycleObservations: [any NSObjectProtocol] private var store: [ObservableKey: [UUID: AsyncStream<(event: ObservedEvent, priority: Priority)>.Continuation]] = [:] + // MARK: - Initialization + + init(using dependencies: Dependencies) { + let notifications: [Notification.Name: AppLifecycle] = [ + UIApplication.didEnterBackgroundNotification: .didEnterBackground, + UIApplication.willEnterForegroundNotification: .willEnterForeground, + UIApplication.didBecomeActiveNotification: .didBecomeActive, + UIApplication.willResignActiveNotification: .willResignActive, + UIApplication.didReceiveMemoryWarningNotification: .didReceiveMemoryWarning, + UIApplication.willTerminateNotification: .willTerminate + ] + + lifecycleObservations = notifications.reduce(into: []) { [dependencies] result, next in + let value: AppLifecycle = next.value + + result.append( + NotificationCenter.default.addObserver(forName: next.key, object: nil, queue: .current) { [dependencies] _ in + dependencies.notifyAsync(key: .appLifecycle(value)) + } + ) + } + } + deinit { + NotificationCenter.default.removeObserver(self) store.values.forEach { $0.values.forEach { $0.finish() } } } diff --git a/SessionUtilitiesKit/Types/UserDefaultsType.swift b/SessionUtilitiesKit/Types/UserDefaultsType.swift index e7db0491ee..968b66c2e4 100644 --- a/SessionUtilitiesKit/Types/UserDefaultsType.swift +++ b/SessionUtilitiesKit/Types/UserDefaultsType.swift @@ -228,6 +228,9 @@ public extension UserDefaults.StringKey { /// The id of the thread that a message was just shared to static let lastSharedThreadId: UserDefaults.StringKey = "lastSharedThreadId" + + /// The app-icon name of the previously selected app icon disguise + static let lastSelectedAppIconDisguise: UserDefaults.StringKey = "lastSelectedAppIconDisguise" } // MARK: - Keys diff --git a/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift b/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift index a6c60c58b8..5101300fc7 100644 --- a/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift +++ b/SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift @@ -15,9 +15,7 @@ class IdentitySpec: QuickSpec { @TestState var dependencies: TestDependencies! = TestDependencies() @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self - ], + migrations: [_001_SUK_InitialSetupMigration.self], using: dependencies ) diff --git a/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift b/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift index 82b6fe25e2..bfd9d50009 100644 --- a/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift +++ b/SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift @@ -47,14 +47,12 @@ class JobRunnerSpec: QuickSpec { } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), - migrationTargets: [ - SNUtilitiesKit.self + migrations: [ + _001_SUK_InitialSetupMigration.self, + _012_AddJobPriority.self, + _020_AddJobUniqueHash.self ], - using: dependencies, - initialData: { db in - // Migrations add jobs which we don't want so delete them - try Job.deleteAll(db) - } + using: dependencies ) @TestState(singleton: .jobRunner, in: dependencies) var jobRunner: JobRunnerType! = JobRunner( isTestingJobRunner: true, diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift index b8adcf5717..35a39417a9 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift @@ -63,7 +63,8 @@ public class ImageEditorBrushViewController: OWSViewController { paletteView.delegate = self self.view.addSubview(paletteView) paletteView.center(.vertical, in: self.view, withInset: -(bottomInset / 2)) - paletteView.pin(.trailing, to: .trailing, of: self.view) + paletteView.pin(.trailing, to: .trailing, of: self.view, withInset: -Values.smallSpacing) + paletteView.set(.width, to: Values.gradientPaletteWidth) self.view.isUserInteractionEnabled = true diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift index f323b7adb9..3fd75252e0 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift @@ -501,10 +501,12 @@ public class ImageEditorCanvasView: UIView { ] ) let layer = EditorTextLayer(itemId: item.itemId) - layer.string = attributedString - layer.themeForegroundColorForced = .color(item.color.color) - layer.font = CGFont(item.font.fontName as CFString) + // Set as .strings, passing only attributed string does not display text + // `attributedString` is now only used to compute sizes + layer.string = attributedString.string + layer.font = item.font layer.fontSize = fontSize + layer.themeForegroundColorForced = .color(item.color.color) layer.isWrapped = true layer.alignmentMode = CATextLayerAlignmentMode.center // I don't think we need to enable allowsFontSubpixelQuantization diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift index 2d7ec71558..b98469d677 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift @@ -227,11 +227,13 @@ public class ImageEditorPaletteView: UIView { } addSubview(imageView) // We use an invisible margin to expand the hot area of this control. - let margin: CGFloat = 20 - imageView.pin(.top, to: .top, of: self, withInset: margin) - imageView.pin(.leading, to: .leading, of: self, withInset: -margin) - imageView.pin(.trailing, to: .trailing, of: self, withInset: margin) - imageView.pin(.bottom, to: .bottom, of: self, withInset: -margin) + let verticalMargin: CGFloat = 20 + let horizontalMargin: CGFloat = 8 + + imageView.pin(.top, to: .top, of: self, withInset: verticalMargin) + imageView.pin(.leading, to: .leading, of: self, withInset: -horizontalMargin) + imageView.pin(.trailing, to: .trailing, of: self, withInset: horizontalMargin) + imageView.pin(.bottom, to: .bottom, of: self, withInset: -verticalMargin) imageView.themeBorderColor = .white imageView.layer.borderWidth = 1 diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift index 77d8f261bb..4febd32c7b 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift @@ -190,8 +190,12 @@ public class ImageEditorTextViewController: OWSViewController, VAlignTextViewDel paletteView.delegate = self self.view.addSubview(paletteView) - paletteView.center(.horizontal, in: textView) - paletteView.pin(.trailing, to: .trailing, of: self.view) + paletteView.center(.vertical, in: self.view, withInset: -((bottomInset / 2) + Values.largeSpacing)) + paletteView.pin(.trailing, to: .trailing, of: self.view, withInset: -Values.smallSpacing) + + // Size of gradient image and touchable area + paletteView.set(.width, to: Values.gradientPaletteWidth) + // This will determine the text view's size. paletteView.pin(.leading, to: .trailing, of: textView) diff --git a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift index 1e732e5f1b..4feb892afb 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift @@ -406,12 +406,7 @@ public class MediaMessageView: UIView { ) : stackView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor) ), - - imageView.widthAnchor.constraint( - equalTo: imageView.heightAnchor, - multiplier: clampedRatio - ), - + (maybeImageSize != nil ? imageView.widthAnchor.constraint(equalToConstant: imageSize) : imageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor) @@ -426,11 +421,9 @@ public class MediaMessageView: UIView { equalTo: imageView.centerYAnchor, constant: ceil(imageSize * 0.15) ), - fileTypeImageView.widthAnchor.constraint( - equalTo: fileTypeImageView.heightAnchor, - multiplier: ((fileTypeImageView.image?.size.width ?? 1) / (fileTypeImageView.image?.size.height ?? 1)) - ), - fileTypeImageView.widthAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 0.5), + + fileTypeImageView.widthAnchor.constraint(equalToConstant: imageSize * 0.5), + fileTypeImageView.heightAnchor.constraint(equalToConstant: imageSize * 0.5), loadingView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor), loadingView.centerYAnchor.constraint(equalTo: imageView.centerYAnchor), @@ -438,6 +431,16 @@ public class MediaMessageView: UIView { loadingView.heightAnchor.constraint(equalToConstant: ceil(imageSize / 3)) ]) + if imageView.image?.size == nil { + // Handle `clampedRatio` ratio when image is from data + NSLayoutConstraint.activate([ + imageView.widthAnchor.constraint( + equalTo: imageView.heightAnchor, + multiplier: clampedRatio + ) + ]) + } + // No inset for the text for URLs but there is for all other layouts if !attachment.isUrl { NSLayoutConstraint.activate([ diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index c8daacfd89..1a3f2f7558 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -3,14 +3,13 @@ import Foundation import GRDB import SessionUIKit -import SessionSnodeKit +import SessionNetworkingKit import SessionMessagingKit import SessionUtilitiesKit public enum AppSetup { public static func setupEnvironment( requestId: String? = nil, - additionalMigrationTargets: [MigratableTarget.Type] = [], appSpecificBlock: (() -> ())? = nil, migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil, migrationsCompletion: @escaping (Result) -> (), @@ -43,7 +42,6 @@ public enum AppSetup { runPostSetupMigrations( requestId: requestId, backgroundTask: backgroundTask, - additionalMigrationTargets: additionalMigrationTargets, migrationProgressChanged: migrationProgressChanged, migrationsCompletion: migrationsCompletion, using: dependencies @@ -57,7 +55,6 @@ public enum AppSetup { public static func runPostSetupMigrations( requestId: String? = nil, backgroundTask: SessionBackgroundTask? = nil, - additionalMigrationTargets: [MigratableTarget.Type] = [], migrationProgressChanged: ((CGFloat, TimeInterval) -> ())? = nil, migrationsCompletion: @escaping (Result) -> (), using dependencies: Dependencies @@ -65,12 +62,7 @@ public enum AppSetup { var backgroundTask: SessionBackgroundTask? = (backgroundTask ?? SessionBackgroundTask(label: #function, using: dependencies)) dependencies[singleton: .storage].perform( - migrationTargets: additionalMigrationTargets - .appending(contentsOf: [ - SNUtilitiesKit.self, - SNSnodeKit.self, - SNMessagingKit.self - ]), + migrations: SNMessagingKit.migrations, onProgressUpdate: migrationProgressChanged, onComplete: { originalResult in // Now that the migrations are complete there are a few more states which need diff --git a/_SharedTestUtilities/SynchronousStorage.swift b/_SharedTestUtilities/SynchronousStorage.swift index fef7fd8aae..59510cf436 100644 --- a/_SharedTestUtilities/SynchronousStorage.swift +++ b/_SharedTestUtilities/SynchronousStorage.swift @@ -11,8 +11,7 @@ class SynchronousStorage: Storage, DependenciesSettable, InitialSetupable { public init( customWriter: DatabaseWriter? = nil, - migrationTargets: [MigratableTarget.Type]? = nil, - migrations: [Storage.KeyedMigration]? = nil, + migrations: [Migration.Type]? = nil, using dependencies: Dependencies, initialData: ((ObservingDatabase) throws -> ())? = nil ) { @@ -21,20 +20,9 @@ class SynchronousStorage: Storage, DependenciesSettable, InitialSetupable { super.init(customWriter: customWriter, using: dependencies) - // Process any migration targets first - if let migrationTargets: [MigratableTarget.Type] = migrationTargets { + if let migrations: [Migration.Type] = migrations { perform( - migrationTargets: migrationTargets, - async: false, - onProgressUpdate: nil, - onComplete: { _ in } - ) - } - - // Then process any provided migration info - if let migrations: [Storage.KeyedMigration] = migrations { - perform( - sortedMigrations: migrations, + migrations: migrations, async: false, onProgressUpdate: nil, onComplete: { _ in }