diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 4e79d8979..35489ec2f 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -321,7 +321,7 @@ class XMTPModule : Module() { ).toJson() } - AsyncFunction("sendEncodedContent") { clientAddress: String, topic: String, encodedContentData: List -> + AsyncFunction("sendEncodedContent") { clientAddress: String, topic: String, encodedContentData: List, shouldPush: Boolean -> val conversation = findConversation( clientAddress = clientAddress, @@ -338,8 +338,9 @@ class XMTPModule : Module() { } } val encodedContent = EncodedContent.parseFrom(encodedContentDataBytes) + val options = SendOptions(contentType = encodedContent.type, __shouldPush = shouldPush) - conversation.send(encodedContent = encodedContent) + conversation.send(encodedContent = encodedContent, options = options) } AsyncFunction("listConversations") { clientAddress: String -> @@ -456,7 +457,7 @@ class XMTPModule : Module() { ).toJson() } - AsyncFunction("prepareEncodedMessage") { clientAddress: String, conversationTopic: String, encodedContentData: List -> + AsyncFunction("prepareEncodedMessage") { clientAddress: String, conversationTopic: String, encodedContentData: List, shouldPush: Boolean -> logV("prepareEncodedMessage") val conversation = findConversation( @@ -478,6 +479,7 @@ class XMTPModule : Module() { val prepared = conversation.prepareMessage( encodedContent = encodedContent, + options = SendOptions(contentType = encodedContent.type, __shouldPush = shouldPush) ) val preparedAtMillis = prepared.envelopes[0].timestampNs / 1_000_000 val preparedFile = File.createTempFile(prepared.messageId, null) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt index 775ef66a5..4835dd1bd 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt @@ -20,7 +20,8 @@ class DecodedMessageWrapper { "content" to ContentJson(model.encodedContent).toJsonMap(), "senderAddress" to model.senderAddress, "sent" to model.sentAt.time, - "fallback" to model.encodedContent.fallback + "fallback" to model.encodedContent.fallback, + "shouldPush" to model.shouldPush as Any ) } } diff --git a/example/src/tests.ts b/example/src/tests.ts index 814a091b3..c3ac51161 100644 --- a/example/src/tests.ts +++ b/example/src/tests.ts @@ -58,6 +58,10 @@ class NumberCodec implements JSContentCodec { fallback(content: NumberRef): string | undefined { return 'a billion' } + + shouldPush(content: NumberRef): boolean { + return false + } } export type Test = { @@ -808,6 +812,46 @@ test('register and use custom content types when preparing message', async () => return true }) +test('shouldPush for content types', async () => { + const bob = await Client.createRandom({ + env: 'local', + codecs: [new NumberCodec()], + }) + const alice = await Client.createRandom({ + env: 'local', + codecs: [new NumberCodec()], + }) + + bob.register(new NumberCodec()) + alice.register(new NumberCodec()) + + const bobConvo = await bob.conversations.newConversation(alice.address) + const aliceConvo = await alice.conversations.newConversation(bob.address) + + await bobConvo.send('hi') + + const messages = await aliceConvo.messages() + + const message = messages[0] + assert(message.shouldPush === true, 'shouldPush should be true') + + const prepped = await bobConvo.prepareMessage( + { topNumber: { bottomNumber: 12 } }, + { + contentType: ContentTypeNumber, + } + ) + + await bobConvo.sendPreparedMessage(prepped) + + const messages2 = await aliceConvo.messages() + const message2 = messages2[0] + + assert(message2.shouldPush === false, 'shouldPush should be false') + + return true +}) + test('calls preCreateIdentityCallback when supplied', async () => { let isCallbackCalled = false const preCreateIdentityCallback = () => { diff --git a/ios/Wrappers/DecodedMessageWrapper.swift b/ios/Wrappers/DecodedMessageWrapper.swift index 72d6c2368..40514b1a2 100644 --- a/ios/Wrappers/DecodedMessageWrapper.swift +++ b/ios/Wrappers/DecodedMessageWrapper.swift @@ -13,6 +13,7 @@ struct DecodedMessageWrapper { "senderAddress": model.senderAddress, "sent": UInt64(model.sentAt.timeIntervalSince1970 * 1000), "fallback": model.encodedContent.fallback, + "shouldPush": model.shouldPush as Any ] } diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 5ab661e2d..28f0c69fa 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -226,14 +226,16 @@ public class XMTPModule: Module { ).toJson() } - AsyncFunction("sendEncodedContent") { (clientAddress: String, topic: String, encodedContentData: [UInt8]) -> String in + AsyncFunction("sendEncodedContent") { (clientAddress: String, topic: String, encodedContentData: [UInt8], shouldPush: Bool) -> String in guard let conversation = try await findConversation(clientAddress: clientAddress, topic: topic) else { throw Error.conversationNotFound("no conversation found for \(topic)") } let encodedContent = try EncodedContent(serializedData: Data(encodedContentData)) + + let options = SendOptions(contentType: encodedContent.type, __shouldPush: shouldPush) - return try await conversation.send(encodedContent: encodedContent) + return try await conversation.send(encodedContent: encodedContent, options: options) } AsyncFunction("listConversations") { (clientAddress: String) -> [String] in @@ -390,15 +392,19 @@ public class XMTPModule: Module { AsyncFunction("prepareEncodedMessage") { ( clientAddress: String, conversationTopic: String, - encodedContentData: [UInt8] + encodedContentData: [UInt8], + shouldPush: Bool ) -> String in guard let conversation = try await findConversation(clientAddress: clientAddress, topic: conversationTopic) else { throw Error.conversationNotFound("no conversation found for \(conversationTopic)") } let encodedContent = try EncodedContent(serializedData: Data(encodedContentData)) + + let contentType = encodedContent.type let prepared = try await conversation.prepareMessage( - encodedContent: encodedContent + encodedContent: encodedContent, + options: SendOptions(contentType: contentType, __shouldPush: shouldPush) ) let preparedAtMillis = prepared.envelopes[0].timestampNs / 1_000_000 let preparedData = try prepared.serializedData() diff --git a/src/index.ts b/src/index.ts index b2039c9ef..9246e20e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -235,12 +235,14 @@ export async function sendWithContentType( } else { const encodedContent = codec.encode(content) encodedContent.fallback = codec.fallback(content) + const shouldPush = codec.shouldPush(content) const encodedContentData = EncodedContent.encode(encodedContent).finish() return await XMTPModule.sendEncodedContent( clientAddress, conversationTopic, - Array.from(encodedContentData) + Array.from(encodedContentData), + shouldPush ) } } @@ -287,11 +289,13 @@ export async function prepareMessageWithContentType( } const encodedContent = codec.encode(content) encodedContent.fallback = codec.fallback(content) + const shouldPush = codec.shouldPush(content) const encodedContentData = EncodedContent.encode(encodedContent).finish() const preparedJson = await XMTPModule.prepareEncodedMessage( clientAddress, conversationTopic, - Array.from(encodedContentData) + Array.from(encodedContentData), + shouldPush ) return JSON.parse(preparedJson) } diff --git a/src/lib/ContentCodec.ts b/src/lib/ContentCodec.ts index b690dd7d8..15e83bb0f 100644 --- a/src/lib/ContentCodec.ts +++ b/src/lib/ContentCodec.ts @@ -101,6 +101,7 @@ export interface JSContentCodec { encode(content: T): EncodedContent decode(encodedContent: EncodedContent): T fallback(content: T): string | undefined + shouldPush(content: T): boolean } export interface NativeContentCodec { @@ -109,6 +110,7 @@ export interface NativeContentCodec { encode(content: T): NativeMessageContent decode(nativeContent: NativeMessageContent): T fallback(content: T): string | undefined + shouldPush(content: T): boolean } export type ContentCodec = JSContentCodec | NativeContentCodec diff --git a/src/lib/DecodedMessage.ts b/src/lib/DecodedMessage.ts index 103e3116c..07ed9043e 100644 --- a/src/lib/DecodedMessage.ts +++ b/src/lib/DecodedMessage.ts @@ -18,6 +18,7 @@ export class DecodedMessage { sent: number // timestamp in milliseconds nativeContent: NativeMessageContent fallback: string | undefined + shouldPush: boolean static from( json: string, @@ -32,7 +33,8 @@ export class DecodedMessage { decoded.senderAddress, decoded.sent, decoded.content, - decoded.fallback + decoded.fallback, + decoded.shouldPush ) } @@ -45,6 +47,7 @@ export class DecodedMessage { sent: number // timestamp in milliseconds content: any fallback: string | undefined + shouldPush: boolean }, client: Client ): DecodedMessage { @@ -56,7 +59,8 @@ export class DecodedMessage { object.senderAddress, object.sent, object.content, - object.fallback + object.fallback, + object.shouldPush ) } @@ -68,7 +72,8 @@ export class DecodedMessage { senderAddress: string, sent: number, content: any, - fallback: string | undefined + fallback: string | undefined, + shouldPush: boolean ) { this.client = client this.id = id @@ -78,6 +83,7 @@ export class DecodedMessage { this.sent = sent this.nativeContent = content this.fallback = fallback + this.shouldPush = shouldPush } content(): ContentTypes { diff --git a/src/lib/NativeCodecs/ReactionCodec.ts b/src/lib/NativeCodecs/ReactionCodec.ts index 9eabf8e6c..80b5fc6bd 100644 --- a/src/lib/NativeCodecs/ReactionCodec.ts +++ b/src/lib/NativeCodecs/ReactionCodec.ts @@ -35,4 +35,15 @@ export class ReactionCodec implements NativeContentCodec { return undefined } } + + shouldPush(content: ReactionContent): boolean { + switch (content.action) { + case 'added': + return true + case 'removed': + return false + default: + return false + } + } } diff --git a/src/lib/NativeCodecs/ReadReceiptCodec.ts b/src/lib/NativeCodecs/ReadReceiptCodec.ts index e99979e3c..cfcb8ad23 100644 --- a/src/lib/NativeCodecs/ReadReceiptCodec.ts +++ b/src/lib/NativeCodecs/ReadReceiptCodec.ts @@ -30,4 +30,8 @@ export class ReadReceiptCodec fallback(content: object): string | undefined { return undefined } + + shouldPush(content: object): boolean { + return false + } } diff --git a/src/lib/NativeCodecs/RemoteAttachmentCodec.ts b/src/lib/NativeCodecs/RemoteAttachmentCodec.ts index 026a2767c..354a52584 100644 --- a/src/lib/NativeCodecs/RemoteAttachmentCodec.ts +++ b/src/lib/NativeCodecs/RemoteAttachmentCodec.ts @@ -30,4 +30,8 @@ export class RemoteAttachmentCodec fallback(content: RemoteAttachmentContent): string | undefined { return `Can’t display "${content.filename}". This app doesn’t support attachments.` } + + shouldPush(content: RemoteAttachmentContent): boolean { + return true + } } diff --git a/src/lib/NativeCodecs/ReplyCodec.ts b/src/lib/NativeCodecs/ReplyCodec.ts index 10cecb4ce..c58bd016c 100644 --- a/src/lib/NativeCodecs/ReplyCodec.ts +++ b/src/lib/NativeCodecs/ReplyCodec.ts @@ -36,4 +36,8 @@ export class ReplyCodec implements NativeContentCodec { } return 'Replied to an earlier message' } + + shouldPush(content: ReplyContent): boolean { + return true + } } diff --git a/src/lib/NativeCodecs/StaticAttachmentCodec.ts b/src/lib/NativeCodecs/StaticAttachmentCodec.ts index c0c0acd3b..722d317d0 100644 --- a/src/lib/NativeCodecs/StaticAttachmentCodec.ts +++ b/src/lib/NativeCodecs/StaticAttachmentCodec.ts @@ -30,4 +30,8 @@ export class StaticAttachmentCodec fallback(content: StaticAttachmentContent): string | undefined { return `Can’t display "${content.filename}". This app doesn’t support attachments.` } + + shouldPush(content: StaticAttachmentContent): boolean { + return true + } } diff --git a/src/lib/NativeCodecs/TextCodec.ts b/src/lib/NativeCodecs/TextCodec.ts index f61101460..b85ca540b 100644 --- a/src/lib/NativeCodecs/TextCodec.ts +++ b/src/lib/NativeCodecs/TextCodec.ts @@ -27,4 +27,8 @@ export class TextCodec implements NativeContentCodec { fallback(content: string): string | undefined { return content } + + shouldPush(content: string): boolean { + return true + } }