diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index d41863b3..a707c428 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -713,6 +713,26 @@ class XMTPModule : Module() { } } + AsyncFunction("prepareEncodedMessage") Coroutine { installationId: String, conversationId: String, encodedContentData: List -> + withContext(Dispatchers.IO) { + logV("prepareEncodedMessage") + val client = clients[installationId] ?: throw XMTPException("No client") + val conversation = client.findConversation(conversationId) + ?: throw XMTPException("no conversation found for $conversationId") + val encodedContentDataBytes = + encodedContentData.foldIndexed(ByteArray(encodedContentData.size)) { i, a, v -> + a.apply { + set( + i, + v.toByte() + ) + } + } + val encodedContent = EncodedContent.parseFrom(encodedContentDataBytes) + conversation.prepareMessage(encodedContent = encodedContent) + } + } + AsyncFunction("findOrCreateDm") Coroutine { installationId: String, peerAddress: String -> withContext(Dispatchers.IO) { logV("findOrCreateDm") diff --git a/src/index.ts b/src/index.ts index 8ce6af61..29e87b54 100644 --- a/src/index.ts +++ b/src/index.ts @@ -596,6 +596,25 @@ export async function prepareMessage( ) } +export async function prepareMessageWithContentType( + installationId: InstallationId, + conversationId: ConversationId, + content: any, + codec: ContentCodec +): Promise { + if ('contentKey' in codec) { + return prepareMessage(installationId, conversationId, content) + } + const encodedContent = codec.encode(content) + encodedContent.fallback = codec.fallback(content) + const encodedContentData = EncodedContent.encode(encodedContent).finish() + return await XMTPModule.prepareEncodedMessage( + installationId, + conversationId, + Array.from(encodedContentData) + ) +} + export async function findOrCreateDm< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( diff --git a/src/lib/Conversation.ts b/src/lib/Conversation.ts index a230d673..a801079e 100644 --- a/src/lib/Conversation.ts +++ b/src/lib/Conversation.ts @@ -28,6 +28,10 @@ export interface ConversationBase { content: ConversationSendPayload, opts?: SendOptions ): Promise + prepareMessage( + content: ConversationSendPayload, + opts?: SendOptions + ): Promise sync() messages(opts?: MessagesOptions): Promise[]> streamMessages( diff --git a/src/lib/Dm.ts b/src/lib/Dm.ts index 6b1bfaf9..9619642d 100644 --- a/src/lib/Dm.ts +++ b/src/lib/Dm.ts @@ -113,11 +113,13 @@ export class Dm */ async prepareMessage< SendContentTypes extends DefaultContentTypes = ContentTypes, - >(content: ConversationSendPayload): Promise { - // TODO: Enable other content types - // if (opts && opts.contentType) { - // return await this._sendWithJSCodec(content, opts.contentType) - // } + >( + content: ConversationSendPayload, + opts?: SendOptions + ): Promise { + if (opts && opts.contentType) { + return await this._prepareWithJSCodec(content, opts.contentType) + } try { if (typeof content === 'string') { @@ -135,6 +137,27 @@ export class Dm } } + private async _prepareWithJSCodec( + content: T, + contentType: XMTP.ContentTypeId + ): Promise { + const codec = + this.client.codecRegistry[ + `${contentType.authorityId}/${contentType.typeId}:${contentType.versionMajor}.${contentType.versionMinor}` + ] + + if (!codec) { + throw new Error(`no codec found for: ${contentType}`) + } + + return await XMTP.prepareMessageWithContentType( + this.client.installationId, + this.id, + content, + codec + ) + } + /** * Publish all prepared messages. * diff --git a/src/lib/Group.ts b/src/lib/Group.ts index 08445f9b..82e6d017 100644 --- a/src/lib/Group.ts +++ b/src/lib/Group.ts @@ -84,7 +84,7 @@ export class Group< * Sends a message to the current group. * * @param {string | MessageContent} content - The content of the message. It can be either a string or a structured MessageContent object. - * @returns {Promise} A Promise that resolves to a string identifier for the sent message. + * @returns {Promise} A Promise that resolves to a string identifier for the sent message. * @throws {Error} Throws an error if there is an issue with sending the message. */ async send( @@ -136,16 +136,18 @@ export class Group< * Prepare a group message to be sent. * * @param {string | MessageContent} content - The content of the message. It can be either a string or a structured MessageContent object. - * @returns {Promise} A Promise that resolves to a string identifier for the prepared message to be sent. + * @returns {Promise} A Promise that resolves to a string identifier for the prepared message to be sent. * @throws {Error} Throws an error if there is an issue with sending the message. */ async prepareMessage< SendContentTypes extends DefaultContentTypes = ContentTypes, - >(content: ConversationSendPayload): Promise { - // TODO: Enable other content types - // if (opts && opts.contentType) { - // return await this._sendWithJSCodec(content, opts.contentType) - // } + >( + content: ConversationSendPayload, + opts?: SendOptions + ): Promise { + if (opts && opts.contentType) { + return await this._prepareWithJSCodec(content, opts.contentType) + } try { if (typeof content === 'string') { @@ -163,6 +165,27 @@ export class Group< } } + private async _prepareWithJSCodec( + content: T, + contentType: XMTP.ContentTypeId + ): Promise { + const codec = + this.client.codecRegistry[ + `${contentType.authorityId}/${contentType.typeId}:${contentType.versionMajor}.${contentType.versionMinor}` + ] + + if (!codec) { + throw new Error(`no codec found for: ${contentType}`) + } + + return await XMTP.prepareMessageWithContentType( + this.client.installationId, + this.id, + content, + codec + ) + } + /** * Publish all prepared messages. *