Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for Custom Content Types when preparing messages #198

Merged
merged 3 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ repositories {
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
implementation "org.xmtp:android:0.7.0"
implementation "org.xmtp:android:0.7.1"
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.facebook.react:react-native:0.71.3'
implementation "com.daveanthonythomas.moshipack:moshipack:1.0.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,39 @@ class XMTPModule : Module() {
).toJson()
}

AsyncFunction("prepareEncodedMessage") { clientAddress: String, conversationTopic: String, encodedContentData: List<Int> ->
logV("prepareEncodedMessage")
val conversation =
findConversation(
clientAddress = clientAddress,
topic = conversationTopic
)
?: throw XMTPException("no conversation found for $conversationTopic")

val encodedContentDataBytes =
encodedContentData.foldIndexed(ByteArray(encodedContentData.size)) { i, a, v ->
a.apply {
set(
i,
v.toByte()
)
}
}
val encodedContent = EncodedContent.parseFrom(encodedContentDataBytes)

val prepared = conversation.prepareMessage(
encodedContent = encodedContent,
)
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()
}

AsyncFunction("sendPreparedMessage") { clientAddress: String, preparedLocalMessageJson: String ->
logV("sendPreparedMessage")
val client = clients[clientAddress] ?: throw XMTPException("No client")
Expand Down
14 changes: 7 additions & 7 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ PODS:
- React-jsinspector (0.71.14)
- React-logger (0.71.14):
- glog
- react-native-blob-util (0.19.4):
- react-native-blob-util (0.19.6):
- React-Core
- react-native-encrypted-storage (4.0.3):
- React-Core
Expand Down Expand Up @@ -411,15 +411,15 @@ PODS:
- GenericJSON (~> 2.0)
- Logging (~> 1.0.0)
- secp256k1.swift (~> 0.1)
- XMTP (0.7.3-alpha0):
- XMTP (0.7.4-alpha0):
- Connect-Swift (= 0.3.0)
- GzipSwift
- web3.swift
- XMTPRust (= 0.3.7-beta0)
- XMTPReactNative (0.1.0):
- ExpoModulesCore
- MessagePacker
- XMTP (= 0.7.3-alpha0)
- XMTP (= 0.7.4-alpha0)
- XMTPRust (0.3.7-beta0)
- Yoga (1.14.0)

Expand Down Expand Up @@ -646,7 +646,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: 94cfc1788637ceaf8841ef1f69b10cc0d62baadc
React-jsinspector: 7bf923954b4e035f494b01ac16633963412660d7
React-logger: 655ff5db8bd922acfbe76a4983ffab048916343e
react-native-blob-util: 30a6c9fd067aadf9177e61a998f2c7efb670598d
react-native-blob-util: d8fa1a7f726867907a8e43163fdd8b441d4489ea
react-native-encrypted-storage: db300a3f2f0aba1e818417c1c0a6be549038deb7
react-native-mmkv: e97c0c79403fb94577e5d902ab1ebd42b0715b43
react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc
Expand All @@ -668,11 +668,11 @@ SPEC CHECKSUMS:
secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
SwiftProtobuf: b02b5075dcf60c9f5f403000b3b0c202a11b6ae1
web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959
XMTP: dc02c96b475e326a4a7b3d3912cc45cf3527bd0b
XMTPReactNative: 5c1111c5bd3456e75b3fa67d1ddccabb7a01df11
XMTP: 9ba94e797211aa4f7cbed9ed2a2f4c44d32c8d06
XMTPReactNative: 6f194a2f3ab388d2517f92feae01cff961ee88ab
XMTPRust: 8848a2ba761b2c961d666632f2ad27d1082faa93
Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9

PODFILE CHECKSUM: 522d88edc2d5fac4825e60a121c24abc18983367

COCOAPODS: 1.13.0
COCOAPODS: 1.14.3
36 changes: 36 additions & 0 deletions example/src/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,42 @@ test('register and use custom content types', async () => {
return true
})

test('register and use custom content types when preparing message', 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)

const prepped = await bobConvo.prepareMessage(12, {
contentType: ContentTypeNumber,
})

await bobConvo.sendPreparedMessage(prepped)

const messages = await aliceConvo.messages()
assert(messages.length === 1, 'did not get messages')

const message = messages[0]
const messageContent = message.content()

assert(
messageContent === 12,
'did not get content properly: ' + JSON.stringify(messageContent)
)

return true
})

test('calls preCreateIdentityCallback when supplied', async () => {
let isCallbackCalled = false
const preCreateIdentityCallback = () => {
Expand Down
24 changes: 24 additions & 0 deletions ios/XMTPModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,30 @@ public class XMTPModule: Module {
).toJson()
}

AsyncFunction("prepareEncodedMessage") { (
clientAddress: String,
conversationTopic: String,
encodedContentData: [UInt8]
) -> 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 prepared = try await conversation.prepareMessage(
encodedContent: encodedContent
)
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,
preparedAt: preparedAtMillis
).toJson()
}

AsyncFunction("sendPreparedMessage") { (clientAddress: String, preparedLocalMessageJson: String) -> String in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
Expand Down
2 changes: 1 addition & 1 deletion ios/XMTPReactNative.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ Pod::Spec.new do |s|

s.source_files = "**/*.{h,m,swift}"
s.dependency "MessagePacker"
s.dependency "XMTP", "= 0.7.3-alpha0"
s.dependency "XMTP", "= 0.7.4-alpha0"
end
20 changes: 20 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,26 @@ export async function prepareMessage(
return JSON.parse(preparedJson)
}

export async function prepareMessageWithContentType<T>(
clientAddress: string,
conversationTopic: string,
content: any,
codec: ContentCodec<T>
): Promise<PreparedLocalMessage> {
if ('contentKey' in codec) {
return prepareMessage(clientAddress, conversationTopic, content)
}
const encodedContent = codec.encode(content)
encodedContent.fallback = codec.fallback(content)
const encodedContentData = EncodedContent.encode(encodedContent).finish()
const preparedJson = await XMTPModule.prepareEncodedMessage(
clientAddress,
conversationTopic,
Array.from(encodedContentData)
)
return JSON.parse(preparedJson)
}

export async function sendPreparedMessage(
clientAddress: string,
preparedLocalMessage: PreparedLocalMessage
Expand Down
29 changes: 28 additions & 1 deletion src/lib/Conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,27 @@
}
}

private async _prepareWithJSCodec<T>(
content: T,
contentType: XMTP.ContentTypeId
): Promise<PreparedLocalMessage> {
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.address,
this.topic,
content,
codec,

Check warning on line 151 in src/lib/Conversation.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `,`
)
}

/**
* Prepares a message to be sent, yielding a `PreparedLocalMessage` object.
*
Expand All @@ -146,7 +167,13 @@
* @returns {Promise<PreparedLocalMessage>} A Promise that resolves to a `PreparedLocalMessage` object.
* @throws {Error} Throws an error if there is an issue with preparing the message.
*/
async prepareMessage(content: any): Promise<PreparedLocalMessage> {
async prepareMessage(
content: any,
opts?: SendOptions
): Promise<PreparedLocalMessage> {
if (opts && opts.contentType) {
return await this._prepareWithJSCodec(content, opts.contentType)
}
try {
if (typeof content === 'string') {
content = { text: content }
Expand Down
Loading