diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 149041af4f..c63e1c398c 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -2689,6 +2689,7 @@ E18C4A7729EF2ECC007534D4 /* SignalAccountTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18C4A7629EF2ECC007534D4 /* SignalAccountTest.swift */; }; E1A090382A4B909B00F2BE8B /* RecipientHidingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A090372A4B909B00F2BE8B /* RecipientHidingManager.swift */; }; E1B32F842CA6162A002141F4 /* LinkPreviewCallLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CFAAA22C9DD2B1003145C3 /* LinkPreviewCallLink.swift */; }; + E1B32F892CAB63E0002141F4 /* CallLinkProfileKeySharingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B32F852CAB5923002141F4 /* CallLinkProfileKeySharingManager.swift */; }; E1C2A54B2A8FCB0D00AEC4DA /* DeleteSystemContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C2A54A2A8FCB0D00AEC4DA /* DeleteSystemContactViewController.swift */; }; E1D827D52BD9B6E50022C1AF /* ReactionsBurstView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D827D42BD9B6E50022C1AF /* ReactionsBurstView.swift */; }; E1D827D72BD9DA4D0022C1AF /* ReactionsSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D827D62BD9DA4D0022C1AF /* ReactionsSink.swift */; }; @@ -6330,6 +6331,7 @@ E18C4A7629EF2ECC007534D4 /* SignalAccountTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalAccountTest.swift; sourceTree = ""; }; E1A090372A4B909B00F2BE8B /* RecipientHidingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientHidingManager.swift; sourceTree = ""; }; E1A0AD8B16E13FDD0071E604 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + E1B32F852CAB5923002141F4 /* CallLinkProfileKeySharingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallLinkProfileKeySharingManager.swift; sourceTree = ""; }; E1C2A54A2A8FCB0D00AEC4DA /* DeleteSystemContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteSystemContactViewController.swift; sourceTree = ""; }; E1CFAAA22C9DD2B1003145C3 /* LinkPreviewCallLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewCallLink.swift; sourceTree = ""; }; E1D827D42BD9B6E50022C1AF /* ReactionsBurstView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsBurstView.swift; sourceTree = ""; }; @@ -10597,6 +10599,7 @@ B9C0A96C2C6D3F63001FD0AA /* CallLinkApprovalRequestView.swift */, B93E4B2B2C9A3298005989B6 /* CallLinkApprovalViewModel.swift */, B9DFDAD62C99F68A00588AD1 /* CallLinkBulkApprovalSheet.swift */, + E1B32F852CAB5923002141F4 /* CallLinkProfileKeySharingManager.swift */, 505A254B2CAE7BA700CF4C26 /* CallLinkViewController.swift */, E1E78CAE2B573BD100B6FC2D /* CallMemberCameraOffView.swift */, E1E78CAC2B573B5800B6FC2D /* CallMemberChromeOverlayView.swift */, @@ -15836,6 +15839,7 @@ 50A7830E2C0128D70008030E /* CallLinkCall.swift in Sources */, 500504AE2CA32969003D27ED /* CallLinkFetchJobRunner.swift in Sources */, 50733B1E2BAD038D00CEEC1F /* CallLinkManager.swift in Sources */, + E1B32F892CAB63E0002141F4 /* CallLinkProfileKeySharingManager.swift in Sources */, 50462B2E2C9E441F00FC5920 /* CallLinkStateUpdater.swift in Sources */, 505A254C2CAE7BA700CF4C26 /* CallLinkViewController.swift in Sources */, E1E78CAF2B573BD100B6FC2D /* CallMemberCameraOffView.swift in Sources */, diff --git a/Signal/AppLaunch/AppEnvironment.swift b/Signal/AppLaunch/AppEnvironment.swift index 0b9c207510..932ce0f2b6 100644 --- a/Signal/AppLaunch/AppEnvironment.swift +++ b/Signal/AppLaunch/AppEnvironment.swift @@ -36,6 +36,8 @@ public class AppEnvironment: NSObject { let windowManagerRef = WindowManager() + private(set) var callLinkProfileKeySharingManager: CallLinkProfileKeySharingManager! + private(set) var appIconBadgeUpdater: AppIconBadgeUpdater! private(set) var badgeManager: BadgeManager! private var usernameValidationObserverRef: UsernameValidationObserver? @@ -65,6 +67,11 @@ public class AppEnvironment: NSObject { database: DependenciesBridge.shared.db ) + self.callLinkProfileKeySharingManager = CallLinkProfileKeySharingManager( + db: DependenciesBridge.shared.db, + accountManager: DependenciesBridge.shared.tsAccountManager + ) + appReadiness.runNowOrWhenAppWillBecomeReady { self.badgeManager.startObservingChanges(in: self.databaseStorage) self.appIconBadgeUpdater.startObserving() diff --git a/Signal/Calls/UserInterface/CallLinkProfileKeySharingManager.swift b/Signal/Calls/UserInterface/CallLinkProfileKeySharingManager.swift new file mode 100644 index 0000000000..3fbb2aed84 --- /dev/null +++ b/Signal/Calls/UserInterface/CallLinkProfileKeySharingManager.swift @@ -0,0 +1,119 @@ +// +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only +// + +import LibSignalClient +import SignalServiceKit + +public class CallLinkProfileKeySharingManager { + private var consideredAcis = Set() + + private let db: any DB + private let accountManager: TSAccountManager + + init(db: any DB, accountManager: TSAccountManager) { + self.db = db + self.accountManager = accountManager + } + + @MainActor + func sendProfileKeyToCallMembers( + acis: [Aci], + blockingManager: BlockingManager + ) { + var unconsideredAcis = [Aci]() + + for aci in acis { + if !consideredAcis.contains(aci) { + unconsideredAcis.append(aci) + } + } + + let eligibleAcisNotSentProfileKeyYet = unconsideredAcis.filter { aci in + return db.read { tx in + let isLocal = accountManager.localIdentifiers(tx: tx)?.aci == aci + + let address = SignalServiceAddress(aci) + let isBlocked = blockingManager.isAddressBlocked( + address, + transaction: SDSDB.shimOnlyBridge(tx) + ) + + let isEligible = !isLocal && !isBlocked + + if !isEligible { + consideredAcis.insert(aci) + } + + return isEligible + } + } + + if eligibleAcisNotSentProfileKeyYet.isEmpty { return } + + self.consideredAcis.formUnion(eligibleAcisNotSentProfileKeyYet) + db.asyncWrite { tx in + for aci in eligibleAcisNotSentProfileKeyYet { + self.sendProfileKey(aci: aci, tx: tx) + } + } + } + + private func sendProfileKey(aci: Aci, tx: DBWriteTransaction) { + let address = SignalServiceAddress(aci) + if + let thread = TSContactThread.getWithContactAddress( + address, + transaction: SDSDB.shimOnlyBridge(tx) + ) + { + let profileKeyMessage = OWSProfileKeyMessage( + thread: thread, + transaction: SDSDB.shimOnlyBridge(tx) + ) + let preparedMessage = PreparedOutgoingMessage.preprepared( + transientMessageWithoutAttachments: profileKeyMessage + ) + SSKEnvironment.shared.messageSenderJobQueueRef.add( + message: preparedMessage, + transaction: SDSDB.shimOnlyBridge(tx) + ) + } + } +} + +extension CallLinkProfileKeySharingManager: GroupCallObserver { + func groupCallPeekChanged(_ call: GroupCall) { + sendProfileKeyIfApplicable(call: call) + } + + @MainActor + private func sendProfileKeyIfApplicable(call: GroupCall) { + switch call.concreteType { + case .groupThread: + return + case .callLink(let callLinkCall): + if + callLinkCall.localUserHasConsentedToJoin(), + let acis = callLinkCall.ringRtcCall.peekInfo?.joinedMembers.map({ Aci(fromUUID: $0) }) + { + sendProfileKeyToCallMembers( + acis: acis, + blockingManager: SSKEnvironment.shared.blockingManagerRef + ) + } + } + } +} + +private extension CallLinkCall { + func localUserHasConsentedToJoin() -> Bool { + switch self.joinState { + case .notJoined: + return false + case .joining, .pending, .joined: + return true + } + } +} diff --git a/Signal/Calls/UserInterface/GroupCallViewController.swift b/Signal/Calls/UserInterface/GroupCallViewController.swift index 4d5a21c0d0..2be23fafe5 100644 --- a/Signal/Calls/UserInterface/GroupCallViewController.swift +++ b/Signal/Calls/UserInterface/GroupCallViewController.swift @@ -295,6 +295,7 @@ class GroupCallViewController: UIViewController { super.init(nibName: nil, bundle: nil) groupCall.addObserver(self) + groupCall.addObserver(AppEnvironment.shared.callLinkProfileKeySharingManager) NotificationCenter.default.addObserver( self,