session-ios/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+Calls.swift

254 lines
10 KiB
Swift
Raw Normal View History

Merge branch 'feature/session-id-blinding-part-2' into feature/database-refactor # Conflicts: # Podfile # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Closed Groups/EditClosedGroupVC.swift # Session/Closed Groups/NewClosedGroupVC.swift # Session/Conversations/Context Menu/ContextMenuVC+Action.swift # Session/Conversations/Context Menu/ContextMenuVC.swift # Session/Conversations/ConversationMessageMapping.swift # Session/Conversations/ConversationSearch.swift # Session/Conversations/ConversationVC+Interaction.swift # Session/Conversations/ConversationVC.swift # Session/Conversations/ConversationViewItem.h # Session/Conversations/ConversationViewItem.m # Session/Conversations/ConversationViewModel.m # Session/Conversations/Input View/InputView.swift # Session/Conversations/Input View/MentionSelectionView.swift # Session/Conversations/LongTextViewController.swift # Session/Conversations/Message Cells/Content Views/LinkPreviewView.swift # Session/Conversations/Message Cells/MessageCell.swift # Session/Conversations/Message Cells/VisibleMessageCell.swift # Session/Conversations/Settings/OWSConversationSettingsViewController.m # Session/Conversations/Views & Modals/ConversationTitleView.swift # Session/Conversations/Views & Modals/DownloadAttachmentModal.swift # Session/Conversations/Views & Modals/JoinOpenGroupModal.swift # Session/Conversations/Views & Modals/LinkPreviewModal.swift # Session/Conversations/Views & Modals/MessagesTableView.swift # Session/Conversations/Views & Modals/URLModal.swift # Session/Home/GlobalSearch/GlobalSearchViewController.swift # Session/Home/HomeVC.swift # Session/Home/Message Requests/MessageRequestsViewController.swift # Session/Media Viewing & Editing/MediaDetailViewController.m # Session/Media Viewing & Editing/MediaPageViewController.swift # Session/Meta/AppDelegate.m # Session/Meta/AppDelegate.swift # Session/Meta/AppEnvironment.swift # Session/Meta/Signal-Bridging-Header.h # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Notifications/AppNotifications.swift # Session/Open Groups/JoinOpenGroupVC.swift # Session/Settings/NukeDataModal.swift # Session/Settings/SeedModal.swift # Session/Settings/SettingsVC.swift # Session/Settings/ShareLogsModal.swift # Session/Shared/ConversationCell.swift # Session/Shared/UserSelectionVC.swift # Session/Utilities/BackgroundPoller.swift # Session/Utilities/MentionUtilities.swift # Session/Utilities/MockDataGenerator.swift # SessionMessagingKit/Database/OWSPrimaryStorage.m # SessionMessagingKit/Database/SSKPreferences.swift # SessionMessagingKit/Database/Storage+Contacts.swift # SessionMessagingKit/Database/Storage+Jobs.swift # SessionMessagingKit/Database/Storage+Messaging.swift # SessionMessagingKit/Database/Storage+OpenGroups.swift # SessionMessagingKit/Database/TSDatabaseView.m # SessionMessagingKit/File Server/FileServerAPIV2.swift # SessionMessagingKit/Jobs/AttachmentDownloadJob.swift # SessionMessagingKit/Jobs/AttachmentUploadJob.swift # SessionMessagingKit/Jobs/JobQueue.swift # SessionMessagingKit/Jobs/MessageReceiveJob.swift # SessionMessagingKit/Jobs/MessageSendJob.swift # SessionMessagingKit/Jobs/NotifyPNServerJob.swift # SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift # SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift # SessionMessagingKit/Messages/Message+Destination.swift # SessionMessagingKit/Messages/Signal/TSIncomingMessage.h # SessionMessagingKit/Messages/Signal/TSIncomingMessage.m # SessionMessagingKit/Messages/Signal/TSInfoMessage.h # SessionMessagingKit/Messages/Signal/TSInfoMessage.m # SessionMessagingKit/Messages/Signal/TSInteraction.h # SessionMessagingKit/Messages/Signal/TSInteraction.m # SessionMessagingKit/Messages/Signal/TSMessage.h # SessionMessagingKit/Messages/Signal/TSMessage.m # SessionMessagingKit/Open Groups/OpenGroupAPIV2+ObjC.swift # SessionMessagingKit/Open Groups/OpenGroupAPIV2.swift # SessionMessagingKit/Open Groups/OpenGroupManagerV2.swift # SessionMessagingKit/Open Groups/OpenGroupMessageV2.swift # SessionMessagingKit/Sending & Receiving/Mentions/MentionsManager.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver.swift # SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift # SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift # SessionMessagingKit/Sending & Receiving/MessageSender.swift # SessionMessagingKit/Sending & Receiving/Notifications/NotificationsProtocol.h # SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift # SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift # SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift # SessionMessagingKit/Storage.swift # SessionMessagingKit/Threads/Notification+Thread.swift # SessionMessagingKit/Threads/TSContactThread.h # SessionMessagingKit/Threads/TSContactThread.m # SessionMessagingKit/Threads/TSGroupModel.h # SessionMessagingKit/Threads/TSGroupModel.m # SessionMessagingKit/Threads/TSGroupThread.m # SessionMessagingKit/Utilities/General.swift # SessionNotificationServiceExtension/NSENotificationPresenter.swift # SessionNotificationServiceExtension/NotificationServiceExtension.swift # SessionSnodeKit/OnionRequestAPI+Encryption.swift # SessionSnodeKit/OnionRequestAPI.swift # SessionSnodeKit/SnodeAPI.swift # SessionSnodeKit/SnodeMessage.swift # SessionSnodeKit/Storage+SnodeAPI.swift # SessionSnodeKit/Storage.swift # SessionUtilitiesKit/General/Array+Utilities.swift # SessionUtilitiesKit/General/Dictionary+Utilities.swift # SessionUtilitiesKit/General/SNUserDefaults.swift # SessionUtilitiesKit/General/Set+Utilities.swift # SessionUtilitiesKit/Meta/SessionUtilitiesKit.h # SessionUtilitiesKit/Utilities/Optional+Utilities.swift # SessionUtilitiesKit/Utilities/Sodium+Conversion.swift # SignalUtilitiesKit/Configuration.swift # SignalUtilitiesKit/Database/Migrations/OpenGroupServerIdLookupMigration.swift # SignalUtilitiesKit/Messaging/FullTextSearcher.swift # SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift # SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift # SignalUtilitiesKit/To Do/OWSProfileManager.m # SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift # SignalUtilitiesKit/Utilities/UIView+OWS.swift
2022-06-08 06:29:51 +02:00
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import WebRTC
import SessionUtilitiesKit
extension MessageReceiver {
public static func handleCallMessage(_ db: Database, message: CallMessage) throws {
switch message.kind {
case .preOffer: try MessageReceiver.handleNewCallMessage(db, message: message)
case .offer: MessageReceiver.handleOfferCallMessage(db, message: message)
case .answer: MessageReceiver.handleAnswerCallMessage(db, message: message)
case .provisionalAnswer: break // TODO: Implement
case let .iceCandidates(sdpMLineIndexes, sdpMids):
guard let currentWebRTCSession = WebRTCSession.current, currentWebRTCSession.uuid == message.uuid else {
return
}
var candidates: [RTCIceCandidate] = []
let sdps = message.sdps
for i in 0..<sdps.count {
let sdp = sdps[i]
let sdpMLineIndex = sdpMLineIndexes[i]
let sdpMid = sdpMids[i]
let candidate = RTCIceCandidate(sdp: sdp, sdpMLineIndex: Int32(sdpMLineIndex), sdpMid: sdpMid)
candidates.append(candidate)
}
currentWebRTCSession.handleICECandidates(candidates)
case .endCall: MessageReceiver.handleEndCallMessage(db, message: message)
}
}
// MARK: - Specific Handling
private static func handleNewCallMessage(_ db: Database, message: CallMessage) throws {
SNLog("[Calls] Received pre-offer message.")
// It is enough just ignoring the pre offers, other call messages
// for this call would be dropped because of no Session call instance
guard
CurrentAppContext().isMainApp,
let sender: String = message.sender,
(try? Contact.fetchOne(db, id: sender))?.isApproved == true
else { return }
guard let timestamp = message.sentTimestamp, TimestampUtils.isWithinOneMinute(timestamp: timestamp) else {
// Add missed call message for call offer messages from more than one minute
if let interaction: Interaction = try MessageReceiver.insertCallInfoMessage(db, for: message, state: .missed) {
let thread: SessionThread = try SessionThread.fetchOrCreate(db, id: sender, variant: .contact)
Environment.shared?.notificationsManager.wrappedValue?
.notifyUser(
db,
forIncomingCall: interaction,
in: thread
)
}
return
}
guard db[.areCallsEnabled] else {
if let interaction: Interaction = try MessageReceiver.insertCallInfoMessage(db, for: message, state: .permissionDenied) {
let thread: SessionThread = try SessionThread.fetchOrCreate(db, id: sender, variant: .contact)
Environment.shared?.notificationsManager.wrappedValue?
.notifyUser(
db,
forIncomingCall: interaction,
in: thread
)
// Trigger the missed call UI if needed
NotificationCenter.default.post(
name: .missedCall,
object: nil,
userInfo: [ Notification.Key.senderId.rawValue: sender ]
)
}
return
}
// Ensure we have a call manager before continuing
guard let callManager: CallManagerProtocol = Environment.shared?.callManager.wrappedValue else { return }
// Ignore pre offer message after the same call instance has been generated
if let currentCall: CurrentCallProtocol = callManager.currentCall, currentCall.uuid == message.uuid {
return
}
guard callManager.currentCall == nil else {
try MessageReceiver.handleIncomingCallOfferInBusyState(db, message: message)
return
}
let interaction: Interaction? = try MessageReceiver.insertCallInfoMessage(db, for: message)
// Handle UI
callManager.showCallUIForCall(
caller: sender,
uuid: message.uuid,
mode: .answer,
interactionId: interaction?.id
)
}
private static func handleOfferCallMessage(_ db: Database, message: CallMessage) {
SNLog("[Calls] Received offer message.")
// Ensure we have a call manager before continuing
guard
let callManager: CallManagerProtocol = Environment.shared?.callManager.wrappedValue,
let currentCall: CurrentCallProtocol = callManager.currentCall,
currentCall.uuid == message.uuid,
let sdp: String = message.sdps.first
else { return }
let sdpDescription: RTCSessionDescription = RTCSessionDescription(type: .offer, sdp: sdp)
currentCall.didReceiveRemoteSDP(sdp: sdpDescription)
}
private static func handleAnswerCallMessage(_ db: Database, message: CallMessage) {
SNLog("[Calls] Received answer message.")
guard
let currentWebRTCSession: WebRTCSession = WebRTCSession.current,
currentWebRTCSession.uuid == message.uuid,
let callManager: CallManagerProtocol = Environment.shared?.callManager.wrappedValue,
var currentCall: CurrentCallProtocol = callManager.currentCall,
currentCall.uuid == message.uuid,
let sender: String = message.sender
else { return }
guard sender != getUserHexEncodedPublicKey(db) else {
guard !currentCall.hasStartedConnecting else { return }
callManager.dismissAllCallUI()
callManager.reportCurrentCallEnded(reason: .answeredElsewhere)
return
}
guard let sdp: String = message.sdps.first else { return }
let sdpDescription: RTCSessionDescription = RTCSessionDescription(type: .answer, sdp: sdp)
currentCall.hasStartedConnecting = true
currentCall.didReceiveRemoteSDP(sdp: sdpDescription)
callManager.handleAnswerMessage(message)
}
private static func handleEndCallMessage(_ db: Database, message: CallMessage) {
SNLog("[Calls] Received end call message.")
guard
WebRTCSession.current?.uuid == message.uuid,
let callManager: CallManagerProtocol = Environment.shared?.callManager.wrappedValue,
let currentCall: CurrentCallProtocol = callManager.currentCall,
currentCall.uuid == message.uuid,
let sender: String = message.sender
else { return }
callManager.dismissAllCallUI()
callManager.reportCurrentCallEnded(
reason: (sender == getUserHexEncodedPublicKey(db) ?
.declinedElsewhere :
.remoteEnded
)
)
}
// MARK: - Convenience
public static func handleIncomingCallOfferInBusyState(_ db: Database, message: CallMessage) throws {
let messageInfo: CallMessage.MessageInfo = CallMessage.MessageInfo(state: .missed)
guard
let caller: String = message.sender,
let messageInfoData: Data = try? JSONEncoder().encode(messageInfo),
let thread: SessionThread = try SessionThread.fetchOne(db, id: caller),
!thread.isMessageRequest(db)
else { return }
SNLog("[Calls] Sending end call message because there is an ongoing call.")
_ = try Interaction(
serverHash: message.serverHash,
messageUuid: message.uuid,
threadId: thread.id,
authorId: caller,
variant: .infoCall,
body: String(data: messageInfoData, encoding: .utf8),
timestampMs: (
message.sentTimestamp.map { Int64($0) } ??
Int64(floor(Date().timeIntervalSince1970 * 1000))
)
)
.inserted(db)
try MessageSender
.sendNonDurably(
db,
message: CallMessage(
uuid: message.uuid,
kind: .endCall,
sdps: [],
sentTimestampMs: nil // Explicitly nil as it's a separate message from above
),
interactionId: nil, // Explicitly nil as it's a separate message from above
in: thread
)
.retainUntilComplete()
}
@discardableResult public static func insertCallInfoMessage(
_ db: Database,
for message: CallMessage,
state: CallMessage.MessageInfo.State? = nil
) throws -> Interaction? {
guard
(try? Interaction
.filter(Interaction.Columns.variant == Interaction.Variant.infoCall)
.filter(Interaction.Columns.messageUuid == message.uuid)
.isEmpty(db))
.defaulting(to: false),
let sender: String = message.sender,
let thread: SessionThread = try SessionThread.fetchOne(db, id: sender),
!thread.isMessageRequest(db)
else { return nil }
let messageInfo: CallMessage.MessageInfo = CallMessage.MessageInfo(
state: state.defaulting(
to: (sender == getUserHexEncodedPublicKey(db) ?
.outgoing :
.incoming
)
)
)
let timestampMs: Int64 = (
message.sentTimestamp.map { Int64($0) } ??
Int64(floor(Date().timeIntervalSince1970 * 1000))
)
guard let messageInfoData: Data = try? JSONEncoder().encode(messageInfo) else { return nil }
return try Interaction(
serverHash: message.serverHash,
messageUuid: message.uuid,
threadId: thread.id,
authorId: sender,
variant: .infoCall,
body: String(data: messageInfoData, encoding: .utf8),
timestampMs: timestampMs
).inserted(db)
}
}