TM-SGNL-iOS/SignalServiceKit/Calls/GroupCallPeekClient.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

110 lines
3.8 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
public import LibSignalClient
public import SignalRingRTC
public class GroupCallPeekLogger: PrefixedLogger {
public static let shared = GroupCallPeekLogger()
private convenience init() {
self.init(prefix: "GroupCallPeek")
}
}
public class GroupCallPeekClient {
private var sfuUrl: String {
DebugFlags.callingUseTestSFU.get() ? TSConstants.sfuTestURL : TSConstants.sfuURL
}
private let logger = GroupCallPeekLogger.shared
private let db: any DB
private let groupsV2: any GroupsV2
private let sfuClient: SFUClient
// Even though we never use this, we need to retain it to ensure
// `sfuClient` continues to work properly.
private let sfuClientHttpClient: AnyObject
init(
db: any DB,
groupsV2: any GroupsV2
) {
self.db = db
self.groupsV2 = groupsV2
let httpClient = CallHTTPClient()
self.sfuClient = SignalRingRTC.SFUClient(httpClient: httpClient.ringRtcHttpClient)
self.sfuClientHttpClient = httpClient
}
/// Fetch the current group call peek info for the given thread.
@MainActor
public func fetchPeekInfo(groupId: GroupIdentifier) async throws -> PeekInfo {
let secretParams = try self.db.read { tx in
let groupThread = TSGroupThread.fetch(forGroupId: groupId, tx: SDSDB.shimOnlyBridge(tx))
return try (groupThread?.groupModel as? TSGroupModelV2)?.secretParams()
}
guard let secretParams else {
throw OWSGenericError("Can't peek without secret params.")
}
let membershipProof = try await self.fetchGroupMembershipProof(secretParams: secretParams)
let membership = try self.db.read { tx in
try self.groupMemberInfo(forGroupId: groupId, tx: tx)
}
let peekRequest = PeekRequest(
sfuURL: self.sfuUrl,
membershipProof: membershipProof,
groupMembers: membership
)
let peekResponse = await self.sfuClient.peek(request: peekRequest)
if let errorCode = peekResponse.errorStatusCode {
throw OWSGenericError("Failed to peek with status code: \(errorCode)")
} else {
return peekResponse.peekInfo
}
}
/// Fetches a data blob that serves as proof of membership in the group.
/// Used by RingRTC to verify access to group call information.
public func fetchGroupMembershipProof(secretParams: GroupSecretParams) async throws -> Data {
let credential = try await self.groupsV2.fetchGroupExternalCredentials(secretParams: secretParams)
guard let tokenData = credential.token?.data(using: .utf8) else {
throw OWSAssertionError("Invalid credential")
}
return tokenData
}
public func groupMemberInfo(forGroupId groupId: GroupIdentifier, tx: DBReadTransaction) throws -> [GroupMemberInfo] {
let groupThread = TSGroupThread.fetch(forGroupId: groupId, tx: SDSDB.shimOnlyBridge(tx))
guard let groupModel = groupThread?.groupModel as? TSGroupModelV2 else {
throw OWSAssertionError("Expected V2 group model!")
}
let groupV2Params = try groupModel.groupV2Params()
return groupModel.groupMembership.fullMembers.compactMap {
guard let aci = $0.serviceId as? Aci else {
owsFailDebug("Skipping group member, missing uuid")
return nil
}
guard let aciCiphertext = try? groupV2Params.userId(for: aci) else {
owsFailDebug("Skipping group member, missing uuidCipherText")
return nil
}
return GroupMemberInfo(
userId: aci.rawUUID,
userIdCipherText: aciCiphertext
)
}
}
}