From d912adb3bb66c8a2328f7a350d8fff2ca655a2c7 Mon Sep 17 00:00:00 2001 From: Daniel McCartney Date: Mon, 11 Sep 2023 14:33:37 -0700 Subject: [PATCH] feat: add timestamp to prepared messages --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 2 + .../wrappers/PreparedLocalMessage.kt | 3 ++ example/src/tests.ts | 3 ++ ios/Wrappers/DecodedMessageWrapper.swift | 7 ++- ios/XMTPModule.swift | 52 +++++++++++++------ src/XMTP.types.ts | 1 + 6 files changed, 50 insertions(+), 18 deletions(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index ddecbc011..f8b946a20 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -352,11 +352,13 @@ class XMTPModule : Module() { content = sending.content, options = SendOptions(contentType = sending.type) ) + val preparedAtMillis = prepared.envelopes[0].timestampNs / 1_000_000 val preparedFile = File.createTempFile(prepared.messageId, null) preparedFile.writeBytes(prepared.toSerializedData()) PreparedLocalMessage( messageId = prepared.messageId, preparedFileUri = preparedFile.toURI().toString(), + preparedAt = preparedAtMillis, ).toJson() } diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/PreparedLocalMessage.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/PreparedLocalMessage.kt index b31aebee4..7f5f0b043 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/PreparedLocalMessage.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/PreparedLocalMessage.kt @@ -6,6 +6,7 @@ import com.google.gson.JsonParser class PreparedLocalMessage( val messageId: String, val preparedFileUri: String, + val preparedAt: Long, ) { companion object { fun fromJson(json: String): PreparedLocalMessage { @@ -13,6 +14,7 @@ class PreparedLocalMessage( return PreparedLocalMessage( obj.get("messageId").asString, obj.get("preparedFileUri").asString, + obj.get("preparedAt").asNumber.toLong(), ) } } @@ -20,5 +22,6 @@ class PreparedLocalMessage( fun toJson(): String = GsonBuilder().create().toJson(mapOf( "messageId" to messageId, "preparedFileUri" to preparedFileUri, + "preparedAt" to preparedAt, )) } diff --git a/example/src/tests.ts b/example/src/tests.ts index e73f71554..c76df7011 100644 --- a/example/src/tests.ts +++ b/example/src/tests.ts @@ -103,6 +103,9 @@ test("canPrepareMessage", async () => { await delayToPropogate(); const prepared = await bobConversation.prepareMessage("hi"); + if (!prepared.preparedAt) { + throw new Error("missing `preparedAt` on prepared message"); + } // Either of these should work: await bobConversation.sendPreparedMessage(prepared); diff --git a/ios/Wrappers/DecodedMessageWrapper.swift b/ios/Wrappers/DecodedMessageWrapper.swift index 47ba01c42..4c8edf96e 100644 --- a/ios/Wrappers/DecodedMessageWrapper.swift +++ b/ios/Wrappers/DecodedMessageWrapper.swift @@ -273,20 +273,23 @@ struct DecryptedLocalAttachment { struct PreparedLocalMessage { var messageId: String var preparedFileUri: String + var preparedAt: UInt64 static func fromJson(_ json: String) throws -> PreparedLocalMessage { let data = json.data(using: .utf8)! let obj = (try? JSONSerialization.jsonObject(with: data) as? [String: Any]) ?? [:] return PreparedLocalMessage( messageId: obj["messageId"] as? String ?? "", - preparedFileUri: obj["preparedFileUri"] as? String ?? "" + preparedFileUri: obj["preparedFileUri"] as? String ?? "", + preparedAt: UInt64(truncating: obj["preparedAt"] as? NSNumber ?? 0) ) } func toJson() throws -> String { let obj: [String: Any] = [ "messageId": messageId, - "preparedFileUri": preparedFileUri + "preparedFileUri": preparedFileUri, + "preparedAt": preparedAt ] return try obj.toJson() } diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index c19dc21c1..50f65d8b4 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -219,7 +219,7 @@ public class XMTPModule: Module { ) let encoded = try RemoteAttachment.decryptEncoded(encrypted: encrypted) let attachment: Attachment = try encoded.decoded() - let file = FileManager.default.temporaryDirectory.appendingPathComponent(attachment.filename) + let file = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) try attachment.data.write(to: file) return try DecryptedLocalAttachment( fileUri: file.absoluteString, @@ -254,9 +254,14 @@ public class XMTPModule: Module { before: beforeDate, after: afterDate) - let messages = try decodedMessages.map { (msg) in try DecodedMessageWrapper.encode(msg) } - - return messages + return decodedMessages.compactMap { (msg) in + do { + return try DecodedMessageWrapper.encode(msg) + } catch { + print("discarding message, unable to encode wrapper \(msg.id)") + return nil + } + } } AsyncFunction("loadBatchMessages") { (clientAddress: String, topics: [String]) -> [String] in @@ -299,9 +304,14 @@ public class XMTPModule: Module { let decodedMessages = try await client.conversations.listBatchMessages(topics: topicsList) - let messages = try decodedMessages.map { (msg) in try DecodedMessageWrapper.encode(msg) } - - return messages + return decodedMessages.compactMap { (msg) in + do { + return try DecodedMessageWrapper.encode(msg) + } catch { + print("discarding message, unable to encode wrapper \(msg.id)") + return nil + } + } } AsyncFunction("sendMessage") { (clientAddress: String, conversationTopic: String, contentJson: String) -> String in @@ -329,12 +339,14 @@ public class XMTPModule: Module { content: sending.content, options: SendOptions(contentType: sending.type) ) + let preparedAtMillis = prepared.envelopes[0].timestampNs / 1_000_000 let preparedData = try prepared.serializedData() let preparedFile = FileManager.default.temporaryDirectory.appendingPathComponent(prepared.messageID) try preparedData.write(to: preparedFile) return try PreparedLocalMessage( messageId: prepared.messageID, - preparedFileUri: preparedFile.absoluteString + preparedFileUri: preparedFile.absoluteString, + preparedAt: preparedAtMillis ).toJson() } @@ -515,10 +527,14 @@ public class XMTPModule: Module { subscriptions["messages"] = Task { do { for try await message in try await client.conversations.streamAllMessages() { - sendEvent("message", [ - "clientAddress": clientAddress, - "message": try DecodedMessageWrapper.encodeToObj(message) - ]) + do { + sendEvent("message", [ + "clientAddress": clientAddress, + "message": try DecodedMessageWrapper.encodeToObj(message) + ]) + } catch { + print("discarding message, unable to encode wrapper \(message.id)") + } } } catch { print("Error in all messages subscription: \(error)") @@ -536,10 +552,14 @@ public class XMTPModule: Module { subscriptions[conversation.cacheKey(clientAddress)] = Task { do { for try await message in conversation.streamMessages() { - sendEvent("message", [ - "clientAddress": clientAddress, - "message": try DecodedMessageWrapper.encodeToObj(message) - ]) + do { + sendEvent("message", [ + "clientAddress": clientAddress, + "message": try DecodedMessageWrapper.encodeToObj(message) + ]) + } catch { + print("discarding message, unable to encode wrapper \(message.id)") + } } } catch { print("Error in messages subscription: \(error)") diff --git a/src/XMTP.types.ts b/src/XMTP.types.ts index 3c8261d6e..c92ee67a1 100644 --- a/src/XMTP.types.ts +++ b/src/XMTP.types.ts @@ -70,6 +70,7 @@ export type RemoteAttachmentContent = RemoteAttachmentMetadata & { export type PreparedLocalMessage = { messageId: string; preparedFileUri: `file://${string}`; + preparedAt: number; // timestamp in milliseconds } // This contains the contents of a message.