diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index e0e12d41c..d2eb57e1f 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -172,6 +172,7 @@ B822F9C826B376EF003B8CB8 /* DarwinNotificationCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = B822F9C726B376EF003B8CB8 /* DarwinNotificationCenter.m */; }; B822F9C926B376F6003B8CB8 /* DarwinNotificationCenter.h in Headers */ = {isa = PBXBuildFile; fileRef = B822F9C626B376D2003B8CB8 /* DarwinNotificationCenter.h */; settings = {ATTRIBUTES = (Public, ); }; }; B822F9CB26B37798003B8CB8 /* DarwinNotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = B822F9CA26B37798003B8CB8 /* DarwinNotificationName.swift */; }; + B822F9CD26B3C538003B8CB8 /* IndividualCallMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B822F9CC26B3C538003B8CB8 /* IndividualCallMessage.swift */; }; B8269D2925C7A4B400488AB4 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D2825C7A4B400488AB4 /* InputView.swift */; }; B8269D3325C7A8C600488AB4 /* InputViewButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D3225C7A8C600488AB4 /* InputViewButton.swift */; }; B8269D3D25C7B34D00488AB4 /* InputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D3C25C7B34D00488AB4 /* InputTextView.swift */; }; @@ -1176,6 +1177,7 @@ B822F9C626B376D2003B8CB8 /* DarwinNotificationCenter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DarwinNotificationCenter.h; sourceTree = ""; }; B822F9C726B376EF003B8CB8 /* DarwinNotificationCenter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DarwinNotificationCenter.m; sourceTree = ""; }; B822F9CA26B37798003B8CB8 /* DarwinNotificationName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarwinNotificationName.swift; sourceTree = ""; }; + B822F9CC26B3C538003B8CB8 /* IndividualCallMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndividualCallMessage.swift; sourceTree = ""; }; B8269D2825C7A4B400488AB4 /* InputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputView.swift; sourceTree = ""; }; B8269D3225C7A8C600488AB4 /* InputViewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputViewButton.swift; sourceTree = ""; }; B8269D3C25C7B34D00488AB4 /* InputTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputTextView.swift; sourceTree = ""; }; @@ -2474,6 +2476,7 @@ B8F5F60225EDE16F003BF8D4 /* DataExtractionNotification.swift */, C300A5E62554B07300555489 /* ExpirationTimerUpdate.swift */, C3DA9C0625AE7396008F7C7E /* ConfigurationMessage.swift */, + B822F9CC26B3C538003B8CB8 /* IndividualCallMessage.swift */, ); path = "Control Messages"; sourceTree = ""; @@ -4735,6 +4738,7 @@ C352A32F2557549C00338F3E /* NotifyPNServerJob.swift in Sources */, C300A5F22554B09800555489 /* MessageSender.swift in Sources */, C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */, + B822F9CD26B3C538003B8CB8 /* IndividualCallMessage.swift in Sources */, C32C5AAD256DBE8F003C73A2 /* TSInfoMessage.m in Sources */, C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */, C32A026325A801AA000ED5D4 /* NSData+messagePadding.m in Sources */, diff --git a/Session/Calls/CallService.swift b/Session/Calls/CallService.swift index 0f0e339e8..1d0b11b94 100644 --- a/Session/Calls/CallService.swift +++ b/Session/Calls/CallService.swift @@ -815,7 +815,7 @@ extension CallService { } } - fileprivate func postUserNotificationIfNecessary(message: OWSGroupCallMessage, transaction: SDSAnyWriteTransaction) { + fileprivate func postUserNotificationIfNecessary(message: OWSGroupCallMessage, transaction: YapDatabaseReadWriteTransaction) { // The message can't be for the current call guard self.currentCall?.thread.uniqueId != message.uniqueThreadId else { return } // The creator of the call must be known, and it can't be the local user @@ -823,7 +823,7 @@ extension CallService { // The message must have at least one participant guard (message.joinedMemberUuids?.count ?? 0) > 0 else { return } - guard let thread = TSGroupThread.anyFetch(uniqueId: message.uniqueThreadId, transaction: transaction) else { + guard let thread = TSGroupThread.fetch(uniqueId: message.uniqueThreadId, transaction: transaction) else { owsFailDebug("Unknown thread") return } @@ -836,10 +836,10 @@ extension CallService: DatabaseChangeDelegate { public func databaseChangesDidUpdate(databaseChanges: DatabaseChanges) { AssertIsOnMainThread() - owsAssertDebug(AppReadiness.isAppReady) + owsAssertDebug(AppReadiness.isAppReady()) guard let thread = currentCall?.thread, - thread.isGroupThread, + thread.isGroupThread(), databaseChanges.didUpdate(thread: thread) else { return } updateGroupMembersForCurrentCallIfNecessary() @@ -847,14 +847,14 @@ extension CallService: DatabaseChangeDelegate { public func databaseChangesDidUpdateExternally() { AssertIsOnMainThread() - owsAssertDebug(AppReadiness.isAppReady) + owsAssertDebug(AppReadiness.isAppReady()) updateGroupMembersForCurrentCallIfNecessary() } public func databaseChangesDidReset() { AssertIsOnMainThread() - owsAssertDebug(AppReadiness.isAppReady) + owsAssertDebug(AppReadiness.isAppReady()) updateGroupMembersForCurrentCallIfNecessary() } diff --git a/Session/Calls/Individual/IndividualCallService.swift b/Session/Calls/Individual/IndividualCallService.swift index 5799bd4ac..ba9cd80c3 100644 --- a/Session/Calls/Individual/IndividualCallService.swift +++ b/Session/Calls/Individual/IndividualCallService.swift @@ -6,6 +6,7 @@ import Foundation import PromiseKit import SignalRingRTC import WebRTC +import SessionMessagingKit // MARK: - CallService @@ -797,14 +798,16 @@ import WebRTC Logger.info("shouldSendOffer") firstly { () throws -> Promise in - let offerBuilder = SNProtoCallMessageOffer.builder(id: callId) - offerBuilder.setOpaque(opaque) + let message = IndividualCallMessage() + message.callID = callId + message.opaque = opaque switch callMediaType { - case .audioCall: offerBuilder.setType(.offerAudioCall) - case .videoCall: offerBuilder.setType(.offerVideoCall) + case .audioCall: message.kind = .offer(callType: .audio) + case .videoCall: message.kind = .offer(callType: .video) + } + Storage.write { transaction in + MessageSender.send(message, in: call.individualCall.thread, using: transaction) } - let callMessage = OWSOutgoingCallMessage(thread: call.individualCall.thread, offerMessage: try offerBuilder.build(), destinationDeviceId: NSNumber(value: destinationDeviceId)) - return messageSender.sendMessage(.promise, callMessage.asPreparer) }.done { Logger.info("sent offer message to \(call.individualCall.thread.contactSessionID()) device: \((destinationDeviceId != nil) ? String(destinationDeviceId!) : "nil")") try self.callManager.signalingMessageDidSend(callId: callId) @@ -820,10 +823,13 @@ import WebRTC Logger.info("shouldSendAnswer") firstly { () throws -> Promise in - let answerBuilder = SNProtoCallMessageAnswer.builder(id: callId) - answerBuilder.setOpaque(opaque) - let callMessage = OWSOutgoingCallMessage(thread: call.individualCall.thread, answerMessage: try answerBuilder.build(), destinationDeviceId: NSNumber(value: destinationDeviceId)) - return messageSender.sendMessage(.promise, callMessage.asPreparer) + let message = IndividualCallMessage() + message.callID = callId + message.opaque = opaque + message.kind = .answer + Storage.write { transaction in + MessageSender.send(message, in: call.individualCall.thread, using: transaction) + } }.done { Logger.debug("sent answer message to \(call.individualCall.thread.contactSessionID()) device: \((destinationDeviceId != nil) ? String(destinationDeviceId!) : "nil")") try self.callManager.signalingMessageDidSend(callId: callId) diff --git a/SessionMessagingKit/Messages/Control Messages/IndividualCallMessage.swift b/SessionMessagingKit/Messages/Control Messages/IndividualCallMessage.swift new file mode 100644 index 000000000..4167d941b --- /dev/null +++ b/SessionMessagingKit/Messages/Control Messages/IndividualCallMessage.swift @@ -0,0 +1,100 @@ +import SessionUtilitiesKit + +public final class IndividualCallMessage : ControlMessage { + public var callID: UInt64? + public var opaque: Data? + public var kind: Kind? + +// let offerBuilder = SNProtoCallMessageOffer.builder(id: callId) +// offerBuilder.setOpaque(opaque) +// switch callMediaType { +// case .audioCall: offerBuilder.setType(.offerAudioCall) +// case .videoCall: offerBuilder.setType(.offerVideoCall) +// } +// let callMessage = OWSOutgoingCallMessage(thread: call.individualCall.thread, offerMessage: try offerBuilder.build(), destinationDeviceId: NSNumber(value: destinationDeviceId)) + + // MARK: Call Type + public enum CallType : CustomStringConvertible { + case audio, video + + public var description: String { + switch self { + case .audio: return "audio" + case .video: return "video" + } + } + } + + // MARK: Kind + public enum Kind : CustomStringConvertible { + case offer(callType: CallType) + case answer + + public var description: String { + switch self { + case .offer(let callType): return "offer(callType: \(callType))" + case .answer: return "answer" + } + } + } + + // MARK: Initialization + public override init() { super.init() } + + internal init(callID: UInt64, opaque: Data, kind: Kind) { + super.init() + self.callID = callID + self.opaque = opaque + self.kind = kind + } + + // MARK: Validation + public override var isValid: Bool { + guard super.isValid else { return false } + return true + } + + // MARK: Coding + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + public override func encode(with coder: NSCoder) { + super.encode(with: coder) + } + + // MARK: Proto Conversion + public override class func fromProto(_ proto: SNProtoContent) -> IndividualCallMessage? { + guard let callMessage = proto.callMessage else { return nil } + let callID: UInt64 + let opaqueOrNil: Data? + let kind: Kind + if let offer = callMessage.offer { + callID = offer.id + opaqueOrNil = offer.opaque + let callType: CallType = (offer.type == .offerAudioCall) ? .audio : .video + kind = .offer(callType: callType) + } else if let answer = callMessage.answer { + callID = answer.id + opaqueOrNil = answer.opaque + kind = .answer + } else { + return nil + } + guard let opaque = opaqueOrNil else { return nil } + return IndividualCallMessage(callID: callID, opaque: opaque, kind: kind) + } + + public override func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? { + return nil + } + + // MARK: Description + public override var description: String { + """ + IndividualCallMessage( + + ) + """ + } +} diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index a911f2629..650c03ac8 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -354,6 +354,9 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder { if let _value = dataMessage { builder.setDataMessage(_value) } + if let _value = callMessage { + builder.setCallMessage(_value) + } if let _value = receiptMessage { builder.setReceiptMessage(_value) } @@ -379,6 +382,10 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder { proto.dataMessage = valueParam.proto } + @objc public func setCallMessage(_ valueParam: SNProtoCallMessage) { + proto.callMessage = valueParam.proto + } + @objc public func setReceiptMessage(_ valueParam: SNProtoReceiptMessage) { proto.receiptMessage = valueParam.proto } @@ -408,6 +415,8 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder { @objc public let dataMessage: SNProtoDataMessage? + @objc public let callMessage: SNProtoCallMessage? + @objc public let receiptMessage: SNProtoReceiptMessage? @objc public let typingMessage: SNProtoTypingMessage? @@ -418,12 +427,14 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder { private init(proto: SessionProtos_Content, dataMessage: SNProtoDataMessage?, + callMessage: SNProtoCallMessage?, receiptMessage: SNProtoReceiptMessage?, typingMessage: SNProtoTypingMessage?, configurationMessage: SNProtoConfigurationMessage?, dataExtractionNotification: SNProtoDataExtractionNotification?) { self.proto = proto self.dataMessage = dataMessage + self.callMessage = callMessage self.receiptMessage = receiptMessage self.typingMessage = typingMessage self.configurationMessage = configurationMessage @@ -446,6 +457,11 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder { dataMessage = try SNProtoDataMessage.parseProto(proto.dataMessage) } + var callMessage: SNProtoCallMessage? = nil + if proto.hasCallMessage { + callMessage = try SNProtoCallMessage.parseProto(proto.callMessage) + } + var receiptMessage: SNProtoReceiptMessage? = nil if proto.hasReceiptMessage { receiptMessage = try SNProtoReceiptMessage.parseProto(proto.receiptMessage) @@ -472,6 +488,7 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder { let result = SNProtoContent(proto: proto, dataMessage: dataMessage, + callMessage: callMessage, receiptMessage: receiptMessage, typingMessage: typingMessage, configurationMessage: configurationMessage, diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index 37df25bc6..c7de56f0d 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -210,6 +210,15 @@ struct SessionProtos_Content { /// Clears the value of `dataMessage`. Subsequent reads from it will return its default value. mutating func clearDataMessage() {self._dataMessage = nil} + var callMessage: SessionProtos_CallMessage { + get {return _callMessage ?? SessionProtos_CallMessage()} + set {_callMessage = newValue} + } + /// Returns true if `callMessage` has been explicitly set. + var hasCallMessage: Bool {return self._callMessage != nil} + /// Clears the value of `callMessage`. Subsequent reads from it will return its default value. + mutating func clearCallMessage() {self._callMessage = nil} + var receiptMessage: SessionProtos_ReceiptMessage { get {return _receiptMessage ?? SessionProtos_ReceiptMessage()} set {_receiptMessage = newValue} @@ -251,6 +260,7 @@ struct SessionProtos_Content { init() {} fileprivate var _dataMessage: SessionProtos_DataMessage? = nil + fileprivate var _callMessage: SessionProtos_CallMessage? = nil fileprivate var _receiptMessage: SessionProtos_ReceiptMessage? = nil fileprivate var _typingMessage: SessionProtos_TypingMessage? = nil fileprivate var _configurationMessage: SessionProtos_ConfigurationMessage? = nil @@ -1959,6 +1969,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm static let protoMessageName: String = _protobuf_package + ".Content" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "dataMessage"), + 3: .same(proto: "callMessage"), 5: .same(proto: "receiptMessage"), 6: .same(proto: "typingMessage"), 7: .same(proto: "configurationMessage"), @@ -1981,6 +1992,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularMessageField(value: &self._dataMessage) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._callMessage) }() case 5: try { try decoder.decodeSingularMessageField(value: &self._receiptMessage) }() case 6: try { try decoder.decodeSingularMessageField(value: &self._typingMessage) }() case 7: try { try decoder.decodeSingularMessageField(value: &self._configurationMessage) }() @@ -1994,6 +2006,9 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm if let v = self._dataMessage { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } + if let v = self._callMessage { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } if let v = self._receiptMessage { try visitor.visitSingularMessageField(value: v, fieldNumber: 5) } @@ -2011,6 +2026,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm static func ==(lhs: SessionProtos_Content, rhs: SessionProtos_Content) -> Bool { if lhs._dataMessage != rhs._dataMessage {return false} + if lhs._callMessage != rhs._callMessage {return false} if lhs._receiptMessage != rhs._receiptMessage {return false} if lhs._typingMessage != rhs._typingMessage {return false} if lhs._configurationMessage != rhs._configurationMessage {return false} diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 2c6a2de49..2a12949fb 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -36,6 +36,7 @@ message TypingMessage { message Content { optional DataMessage dataMessage = 1; + optional CallMessage callMessage = 3; optional ReceiptMessage receiptMessage = 5; optional TypingMessage typingMessage = 6; optional ConfigurationMessage configurationMessage = 7; @@ -228,7 +229,7 @@ message DataMessage { optional uint64 timestamp = 7; optional Quote quote = 8; repeated Preview preview = 10; - optional GroupCallUpdate groupCallUpdate = 19; + optional GroupCallUpdate groupCallUpdate = 19; optional LokiProfile profile = 101; optional OpenGroupInvitation openGroupInvitation = 102; optional ClosedGroupControlMessage closedGroupControlMessage = 104;