diff --git a/Sources/XMTPiOS/Client.swift b/Sources/XMTPiOS/Client.swift index c9d47ad0..07ba9bad 100644 --- a/Sources/XMTPiOS/Client.swift +++ b/Sources/XMTPiOS/Client.swift @@ -531,7 +531,7 @@ public final class Client { public func findMessage(messageId: String) throws -> Message? { do { - return Message( + return Message.create( client: self, ffiMessage: try ffiClient.message( messageId: messageId.hexToData)) diff --git a/Sources/XMTPiOS/Conversation.swift b/Sources/XMTPiOS/Conversation.swift index 16afbfac..eef871a0 100644 --- a/Sources/XMTPiOS/Conversation.swift +++ b/Sources/XMTPiOS/Conversation.swift @@ -26,7 +26,7 @@ public enum Conversation: Identifiable, Equatable, Hashable { } } - public func lastMessage() async throws -> DecodedMessage? { + public func lastMessage() async throws -> Message? { switch self { case let .group(group): return try await group.lastMessage() @@ -80,7 +80,7 @@ public enum Conversation: Identifiable, Equatable, Hashable { } } - public func processMessage(messageBytes: Data) async throws -> Message { + public func processMessage(messageBytes: Data) async throws -> Message? { switch self { case let .group(group): return try await group.processMessage(messageBytes: messageBytes) @@ -188,7 +188,7 @@ public enum Conversation: Identifiable, Equatable, Hashable { } } - public func streamMessages() -> AsyncThrowingStream { + public func streamMessages() -> AsyncThrowingStream { switch self { case let .group(group): return group.streamMessages() @@ -203,7 +203,7 @@ public enum Conversation: Identifiable, Equatable, Hashable { afterNs: Int64? = nil, direction: SortDirection? = .descending, deliveryStatus: MessageDeliveryStatus = .all - ) async throws -> [DecodedMessage] { + ) async throws -> [Message] { switch self { case let .group(group): return try await group.messages( diff --git a/Sources/XMTPiOS/Conversations.swift b/Sources/XMTPiOS/Conversations.swift index 8e607451..526a1a9e 100644 --- a/Sources/XMTPiOS/Conversations.swift +++ b/Sources/XMTPiOS/Conversations.swift @@ -329,7 +329,7 @@ public actor Conversations { } public func streamAllMessages(type: ConversationType = .all) - -> AsyncThrowingStream + -> AsyncThrowingStream { AsyncThrowingStream { continuation in let ffiStreamActor = FfiStreamActor() @@ -343,13 +343,10 @@ public actor Conversations { } return } - do { - continuation.yield( - try Message(client: self.client, ffiMessage: message) - .decode() - ) - } catch { - print("Error onMessage \(error)") + if let message = Message.create( + client: self.client, ffiMessage: message) + { + continuation.yield(message) } } diff --git a/Sources/XMTPiOS/DecodedMessage.swift b/Sources/XMTPiOS/DecodedMessage.swift deleted file mode 100644 index 99013cf0..00000000 --- a/Sources/XMTPiOS/DecodedMessage.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Foundation - -/// Decrypted messages from a conversation. -public struct DecodedMessage: Sendable { - public var topic: String - - public var id: String = "" - - public var encodedContent: EncodedContent - - /// The wallet address of the sender of the message - public var senderInboxId: String - - /// When the message was sent - public var sent: Date - public var sentNs: Int64 - - public var client: Client - - public var deliveryStatus: MessageDeliveryStatus = .published - - init( - id: String, - client: Client, - topic: String, - encodedContent: EncodedContent, - senderInboxId: String, - sent: Date, - sentNs: Int64, - deliveryStatus: MessageDeliveryStatus = .published - ) { - self.id = id - self.client = client - self.topic = topic - self.encodedContent = encodedContent - self.senderInboxId = senderInboxId - self.sent = sent - self.sentNs = sentNs - self.deliveryStatus = deliveryStatus - } - - public func content() throws -> T { - return try encodedContent.decoded(with: client) - } - - public var fallbackContent: String { - encodedContent.fallback - } - - var body: String { - do { - return try content() - } catch { - return fallbackContent - } - } -} diff --git a/Sources/XMTPiOS/Dm.swift b/Sources/XMTPiOS/Dm.swift index 9078e2da..96874454 100644 --- a/Sources/XMTPiOS/Dm.swift +++ b/Sources/XMTPiOS/Dm.swift @@ -70,11 +70,11 @@ public struct Dm: Identifiable, Equatable, Hashable { return try ffiConversation.consentState().fromFFI } - public func processMessage(messageBytes: Data) async throws -> Message { + public func processMessage(messageBytes: Data) async throws -> Message? { let message = try await ffiConversation.processStreamedConversationMessage( envelopeBytes: messageBytes) - return Message(client: client, ffiMessage: message) + return Message.create(client: client, ffiMessage: message) } public func send(content: T, options: SendOptions? = nil) async throws @@ -167,7 +167,7 @@ public struct Dm: Identifiable, Equatable, Hashable { self.streamHolder.stream?.end() } - public func streamMessages() -> AsyncThrowingStream { + public func streamMessages() -> AsyncThrowingStream { AsyncThrowingStream { continuation in let task = Task.detached { self.streamHolder.stream = await self.ffiConversation.stream( @@ -177,14 +177,10 @@ public struct Dm: Identifiable, Equatable, Hashable { continuation.finish() return } - do { - continuation.yield( - try Message( - client: self.client, ffiMessage: message - ).decode()) - } catch { - print("Error onMessage \(error)") - continuation.finish(throwing: error) + if let message = Message.create( + client: self.client, ffiMessage: message) + { + continuation.yield(message) } } ) @@ -201,10 +197,9 @@ public struct Dm: Identifiable, Equatable, Hashable { } } - public func lastMessage() async throws -> DecodedMessage? { + public func lastMessage() async throws -> Message? { if let ffiMessage = ffiLastMessage { - return Message(client: self.client, ffiMessage: ffiMessage) - .decodeOrNull() + return Message.create(client: self.client, ffiMessage: ffiMessage) } else { return try await messages(limit: 1).first } @@ -216,7 +211,7 @@ public struct Dm: Identifiable, Equatable, Hashable { limit: Int? = nil, direction: SortDirection? = .descending, deliveryStatus: MessageDeliveryStatus = .all - ) async throws -> [DecodedMessage] { + ) async throws -> [Message] { var options = FfiListMessagesOptions( sentBeforeNs: nil, sentAfterNs: nil, @@ -267,8 +262,7 @@ public struct Dm: Identifiable, Equatable, Hashable { return try await ffiConversation.findMessages(opts: options).compactMap { ffiMessage in - return Message(client: self.client, ffiMessage: ffiMessage) - .decodeOrNull() + return Message.create(client: self.client, ffiMessage: ffiMessage) } } } diff --git a/Sources/XMTPiOS/Group.swift b/Sources/XMTPiOS/Group.swift index 33ec9e30..d1afcc65 100644 --- a/Sources/XMTPiOS/Group.swift +++ b/Sources/XMTPiOS/Group.swift @@ -269,10 +269,10 @@ public struct Group: Identifiable, Equatable, Hashable { return try ffiGroup.consentState().fromFFI } - public func processMessage(messageBytes: Data) async throws -> Message { + public func processMessage(messageBytes: Data) async throws -> Message? { let message = try await ffiGroup.processStreamedConversationMessage( envelopeBytes: messageBytes) - return Message(client: client, ffiMessage: message) + return Message.create(client: client, ffiMessage: message) } public func send(content: T, options: SendOptions? = nil) async throws @@ -365,7 +365,7 @@ public struct Group: Identifiable, Equatable, Hashable { self.streamHolder.stream?.end() } - public func streamMessages() -> AsyncThrowingStream { + public func streamMessages() -> AsyncThrowingStream { AsyncThrowingStream { continuation in let task = Task.detached { self.streamHolder.stream = await self.ffiGroup.stream( @@ -375,14 +375,10 @@ public struct Group: Identifiable, Equatable, Hashable { continuation.finish() return } - do { - continuation.yield( - try Message( - client: self.client, ffiMessage: message - ).decode()) - } catch { - print("Error onMessage \(error)") - continuation.finish(throwing: error) + if let message = Message.create( + client: self.client, ffiMessage: message) + { + continuation.yield(message) } } ) @@ -399,10 +395,9 @@ public struct Group: Identifiable, Equatable, Hashable { } } - public func lastMessage() async throws -> DecodedMessage? { + public func lastMessage() async throws -> Message? { if let ffiMessage = ffiLastMessage { - return Message(client: self.client, ffiMessage: ffiMessage) - .decodeOrNull() + return Message.create(client: self.client, ffiMessage: ffiMessage) } else { return try await messages(limit: 1).first } @@ -414,7 +409,7 @@ public struct Group: Identifiable, Equatable, Hashable { limit: Int? = nil, direction: SortDirection? = .descending, deliveryStatus: MessageDeliveryStatus = .all - ) async throws -> [DecodedMessage] { + ) async throws -> [Message] { var options = FfiListMessagesOptions( sentBeforeNs: nil, sentAfterNs: nil, @@ -464,8 +459,7 @@ public struct Group: Identifiable, Equatable, Hashable { return try await ffiGroup.findMessages(opts: options).compactMap { ffiMessage in - return Message(client: self.client, ffiMessage: ffiMessage) - .decodeOrNull() + return Message.create(client: self.client, ffiMessage: ffiMessage) } } } diff --git a/Sources/XMTPiOS/Libxmtp/Message.swift b/Sources/XMTPiOS/Libxmtp/Message.swift index 1dd5e6d5..dd7bf667 100644 --- a/Sources/XMTPiOS/Libxmtp/Message.swift +++ b/Sources/XMTPiOS/Libxmtp/Message.swift @@ -5,49 +5,45 @@ enum MessageError: Error { case decodeError(String) } -public enum MessageDeliveryStatus: String, RawRepresentable, Sendable { - case all, - published, - unpublished, - failed +public enum MessageDeliveryStatus: String, Sendable { + case all + case published + case unpublished + case failed } public enum SortDirection { - case descending, ascending + case ascending + case descending } public struct Message: Identifiable { - let client: Client let ffiMessage: FfiMessage - - init(client: Client, ffiMessage: FfiMessage) { - self.client = client - self.ffiMessage = ffiMessage - } + private let decodedContent: Any? public var id: String { - return ffiMessage.id.toHex + ffiMessage.id.toHex } - var convoId: String { - return ffiMessage.convoId.toHex + public var convoId: String { + ffiMessage.convoId.toHex } - var senderInboxId: String { - return ffiMessage.senderInboxId + public var senderInboxId: String { + ffiMessage.senderInboxId } - var sentAt: Date { - return Date( + public var sentAt: Date { + Date( timeIntervalSince1970: TimeInterval(ffiMessage.sentAtNs) / 1_000_000_000) } - - var sentAtNs: Int64 { - return ffiMessage.sentAtNs + + public var sentAtNs: Int64 { + ffiMessage.sentAtNs } - var deliveryStatus: MessageDeliveryStatus { + public var deliveryStatus: MessageDeliveryStatus { switch ffiMessage.deliveryStatus { case .unpublished: return .unpublished @@ -58,41 +54,59 @@ public struct Message: Identifiable { } } - public func decode() throws -> DecodedMessage { - do { - let encodedContent = try EncodedContent( - serializedData: ffiMessage.content) - - let decodedMessage = DecodedMessage( - id: id, - client: client, - topic: Topic.groupMessage(convoId).description, - encodedContent: encodedContent, - senderInboxId: senderInboxId, - sent: sentAt, - sentNs: sentAtNs, - deliveryStatus: deliveryStatus + public var topic: String { + Topic.groupMessage(convoId).description + } + + public func content() throws -> T { + guard let result = decodedContent as? T else { + throw MessageError.decodeError( + "Decoded content could not be cast to the expected type \(T.self)." ) + } + return result + } - if decodedMessage.encodedContent.type == ContentTypeGroupUpdated - && ffiMessage.kind != .membershipChange - { - throw MessageError.decodeError( - "Error decoding group membership change") + public var fallbackContent: String { + get throws { + try encodedContent.fallback + } + } + + public var body: String { + get throws { + do { + return try content() as String + } catch { + return try fallbackContent } + } + } - return decodedMessage - } catch { - throw MessageError.decodeError( - "Error decoding message: \(error.localizedDescription)") + public var encodedContent: EncodedContent { + get throws { + try EncodedContent(serializedBytes: ffiMessage.content) } } - public func decodeOrNull() -> DecodedMessage? { + public static func create(client: Client, ffiMessage: FfiMessage) + -> Message? + { do { - return try decode() + let encodedContent = try EncodedContent( + serializedBytes: ffiMessage.content) + if encodedContent.type == ContentTypeGroupUpdated + && ffiMessage.kind != .membershipChange + { + throw MessageError.decodeError( + "Error decoding group membership change") + } + // Decode the content once during creation + let decodedContent: Any = try encodedContent.decoded(with: client) + return Message( + ffiMessage: ffiMessage, decodedContent: decodedContent) } catch { - print("MESSAGE: discarding message that failed to decode", error) + print("Error creating Message: \(error)") return nil } } diff --git a/Tests/XMTPTests/CodecTests.swift b/Tests/XMTPTests/CodecTests.swift index 2604a2a7..5a6fbfba 100644 --- a/Tests/XMTPTests/CodecTests.swift +++ b/Tests/XMTPTests/CodecTests.swift @@ -84,6 +84,6 @@ class CodecTests: XCTestCase { let content: Double? = try? messages[0].content() XCTAssertEqual(nil, content) - XCTAssertEqual("pi", messages[0].fallbackContent) + XCTAssertEqual("pi", try messages[0].fallbackContent) } } diff --git a/Tests/XMTPTests/DmTests.swift b/Tests/XMTPTests/DmTests.swift index c833ab04..4084d7ef 100644 --- a/Tests/XMTPTests/DmTests.swift +++ b/Tests/XMTPTests/DmTests.swift @@ -149,7 +149,7 @@ class DmTests: XCTestCase { try await dm.sync() let firstMessage = try await dm.messages().first! - XCTAssertEqual(firstMessage.body, "gm") + XCTAssertEqual(try firstMessage.body, "gm") XCTAssertEqual(firstMessage.id, messageId) XCTAssertEqual(firstMessage.deliveryStatus, .published) let messages = try await dm.messages() @@ -161,7 +161,7 @@ class DmTests: XCTestCase { let sameMessages = try await sameDm.messages() XCTAssertEqual(sameMessages.count, 2) - XCTAssertEqual(sameMessages.first!.body, "gm") + XCTAssertEqual(try sameMessages.first!.body, "gm") } func testCanStreamDmMessages() async throws { diff --git a/Tests/XMTPTests/GroupTests.swift b/Tests/XMTPTests/GroupTests.swift index acaaa641..b8cc4bd6 100644 --- a/Tests/XMTPTests/GroupTests.swift +++ b/Tests/XMTPTests/GroupTests.swift @@ -694,8 +694,8 @@ class GroupTests: XCTestCase { let alixMessages = try await alixGroup.messages() for message in alixMessages { print( - "message", message.encodedContent.type, - message.encodedContent.type.typeID) + "message", try message.encodedContent.type, + try message.encodedContent.type.typeID) } XCTAssertEqual( alixMessages.count, 5, @@ -708,8 +708,8 @@ class GroupTests: XCTestCase { let boMessages2 = try await boGroup.messages() for message in boMessages2 { print( - "message", message.encodedContent.type, - message.encodedContent.type.typeID) + "message", try message.encodedContent.type, + try message.encodedContent.type.typeID) } XCTAssertEqual( boMessages2.count, 5, diff --git a/Tests/XMTPTests/ReadReceiptTests.swift b/Tests/XMTPTests/ReadReceiptTests.swift index 431a4a69..3495745c 100644 --- a/Tests/XMTPTests/ReadReceiptTests.swift +++ b/Tests/XMTPTests/ReadReceiptTests.swift @@ -24,7 +24,7 @@ class ReadReceiptTests: XCTestCase { _ = try await conversation.messages() let message = try await conversation.messages()[0] - let contentType: String = message.encodedContent.type.typeID + let contentType: String = try message.encodedContent.type.typeID XCTAssertEqual("readReceipt", contentType) } }