mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Removed the 'immediatelyOnMain' extensions as they would break in some cases (eg. upstream errors with multiple 'receive(on:)' calls) resulting in logic running on unexpected threads Updated the ReplaySubject to add subscribers in an Atomic just to be safe Updated the code to remove the invalid open group when the user receives an error after joining Fixed a bug with editing closed group members Fixed broken unit tests
263 lines
8.7 KiB
Swift
263 lines
8.7 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import Foundation
|
|
import GRDB
|
|
import SessionUtilitiesKit
|
|
|
|
/// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information.
|
|
public final class CallMessage: ControlMessage {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case uuid
|
|
case kind
|
|
case sdps
|
|
}
|
|
|
|
public var uuid: String
|
|
public var kind: Kind
|
|
|
|
/// See https://developer.mozilla.org/en-US/docs/Glossary/SDP for more information.
|
|
public var sdps: [String]
|
|
|
|
public override var isSelfSendValid: Bool {
|
|
switch kind {
|
|
case .answer, .endCall: return true
|
|
default: return false
|
|
}
|
|
}
|
|
|
|
// MARK: - Kind
|
|
|
|
/// **Note:** Multiple ICE candidates may be batched together for performance
|
|
public enum Kind: Codable, CustomStringConvertible {
|
|
private enum CodingKeys: String, CodingKey {
|
|
case description
|
|
case sdpMLineIndexes
|
|
case sdpMids
|
|
}
|
|
|
|
case preOffer
|
|
case offer
|
|
case answer
|
|
case provisionalAnswer
|
|
case iceCandidates(sdpMLineIndexes: [UInt32], sdpMids: [String])
|
|
case endCall
|
|
|
|
public var description: String {
|
|
switch self {
|
|
case .preOffer: return "preOffer"
|
|
case .offer: return "offer"
|
|
case .answer: return "answer"
|
|
case .provisionalAnswer: return "provisionalAnswer"
|
|
case .iceCandidates(_, _): return "iceCandidates"
|
|
case .endCall: return "endCall"
|
|
}
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
// Compare the descriptions to find the appropriate case
|
|
let description: String = try container.decode(String.self, forKey: .description)
|
|
|
|
switch description {
|
|
case Kind.preOffer.description: self = .preOffer
|
|
case Kind.offer.description: self = .offer
|
|
case Kind.answer.description: self = .answer
|
|
case Kind.provisionalAnswer.description: self = .provisionalAnswer
|
|
|
|
case Kind.iceCandidates(sdpMLineIndexes: [], sdpMids: []).description:
|
|
self = .iceCandidates(
|
|
sdpMLineIndexes: try container.decode([UInt32].self, forKey: .sdpMLineIndexes),
|
|
sdpMids: try container.decode([String].self, forKey: .sdpMids)
|
|
)
|
|
|
|
case Kind.endCall.description: self = .endCall
|
|
|
|
default: fatalError("Invalid case when trying to decode ClosedGroupControlMessage.Kind")
|
|
}
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(description, forKey: .description)
|
|
|
|
// Note: If you modify the below make sure to update the above 'init(from:)' method
|
|
switch self {
|
|
case .preOffer: break // Only 'description'
|
|
case .offer: break // Only 'description'
|
|
case .answer: break // Only 'description'
|
|
case .provisionalAnswer: break // Only 'description'
|
|
case .iceCandidates(let sdpMLineIndexes, let sdpMids):
|
|
try container.encode(sdpMLineIndexes, forKey: .sdpMLineIndexes)
|
|
try container.encode(sdpMids, forKey: .sdpMids)
|
|
|
|
case .endCall: break // Only 'description'
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Initialization
|
|
|
|
public init(
|
|
uuid: String,
|
|
kind: Kind,
|
|
sdps: [String],
|
|
sentTimestampMs: UInt64? = nil
|
|
) {
|
|
self.uuid = uuid
|
|
self.kind = kind
|
|
self.sdps = sdps
|
|
|
|
super.init(sentTimestamp: sentTimestampMs)
|
|
}
|
|
|
|
// MARK: - Codable
|
|
|
|
required init(from decoder: Decoder) throws {
|
|
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
self.uuid = try container.decode(String.self, forKey: .uuid)
|
|
self.kind = try container.decode(Kind.self, forKey: .kind)
|
|
self.sdps = try container.decode([String].self, forKey: .sdps)
|
|
|
|
try super.init(from: decoder)
|
|
}
|
|
|
|
public override func encode(to encoder: Encoder) throws {
|
|
try super.encode(to: encoder)
|
|
|
|
var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(uuid, forKey: .uuid)
|
|
try container.encode(kind, forKey: .kind)
|
|
try container.encode(sdps, forKey: .sdps)
|
|
}
|
|
|
|
// MARK: - Proto Conversion
|
|
|
|
public override class func fromProto(_ proto: SNProtoContent, sender: String) -> CallMessage? {
|
|
guard let callMessageProto = proto.callMessage else { return nil }
|
|
|
|
let kind: Kind
|
|
|
|
switch callMessageProto.type {
|
|
case .preOffer: kind = .preOffer
|
|
case .offer: kind = .offer
|
|
case .answer: kind = .answer
|
|
case .provisionalAnswer: kind = .provisionalAnswer
|
|
case .iceCandidates:
|
|
kind = .iceCandidates(
|
|
sdpMLineIndexes: callMessageProto.sdpMlineIndexes,
|
|
sdpMids: callMessageProto.sdpMids
|
|
)
|
|
|
|
case .endCall: kind = .endCall
|
|
}
|
|
|
|
let sdps = callMessageProto.sdps
|
|
let uuid = callMessageProto.uuid
|
|
|
|
return CallMessage(
|
|
uuid: uuid,
|
|
kind: kind,
|
|
sdps: sdps
|
|
)
|
|
}
|
|
|
|
public override func toProto(_ db: Database) -> SNProtoContent? {
|
|
let type: SNProtoCallMessage.SNProtoCallMessageType
|
|
|
|
switch kind {
|
|
case .preOffer: type = .preOffer
|
|
case .offer: type = .offer
|
|
case .answer: type = .answer
|
|
case .provisionalAnswer: type = .provisionalAnswer
|
|
case .iceCandidates(_, _): type = .iceCandidates
|
|
case .endCall: type = .endCall
|
|
}
|
|
|
|
let callMessageProto = SNProtoCallMessage.builder(type: type, uuid: uuid)
|
|
if !sdps.isEmpty {
|
|
callMessageProto.setSdps(sdps)
|
|
}
|
|
|
|
if case let .iceCandidates(sdpMLineIndexes, sdpMids) = kind {
|
|
callMessageProto.setSdpMlineIndexes(sdpMLineIndexes)
|
|
callMessageProto.setSdpMids(sdpMids)
|
|
}
|
|
|
|
let contentProto = SNProtoContent.builder()
|
|
do {
|
|
contentProto.setCallMessage(try callMessageProto.build())
|
|
|
|
return try contentProto.build()
|
|
}
|
|
catch {
|
|
SNLog("Couldn't construct call message proto from: \(self).")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// MARK: - Description
|
|
|
|
public var description: String {
|
|
"""
|
|
CallMessage(
|
|
uuid: \(uuid),
|
|
kind: \(kind.description),
|
|
sdps: \(sdps.description)
|
|
)
|
|
"""
|
|
}
|
|
}
|
|
|
|
// MARK: - Convenience
|
|
|
|
public extension CallMessage {
|
|
struct MessageInfo: Codable {
|
|
public enum State: Codable {
|
|
case incoming
|
|
case outgoing
|
|
case missed
|
|
case permissionDenied
|
|
case unknown
|
|
}
|
|
|
|
public let state: State
|
|
|
|
// MARK: - Initialization
|
|
|
|
public init(state: State) {
|
|
self.state = state
|
|
}
|
|
|
|
// MARK: - Content
|
|
|
|
func previewText(threadContactDisplayName: String) -> String {
|
|
switch state {
|
|
case .incoming:
|
|
return String(
|
|
format: "call_incoming".localized(),
|
|
threadContactDisplayName
|
|
)
|
|
|
|
case .outgoing:
|
|
return String(
|
|
format: "call_outgoing".localized(),
|
|
threadContactDisplayName
|
|
)
|
|
|
|
case .missed, .permissionDenied:
|
|
return String(
|
|
format: "call_missed".localized(),
|
|
threadContactDisplayName
|
|
)
|
|
|
|
// TODO: We should do better here
|
|
case .unknown: return ""
|
|
}
|
|
}
|
|
}
|
|
}
|