Skip to content

Commit

Permalink
Merge pull request #77 from xmtp/np/list-batch-messages
Browse files Browse the repository at this point in the history
List batch messages takes query parameter
  • Loading branch information
nplasterer authored Jul 21, 2023
2 parents 3b7855c + 692dc2e commit d9f5e8a
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 67 deletions.
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ repositories {
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
implementation "org.xmtp:android:0.3.1"
implementation "org.xmtp:android:0.3.2"
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 @@ -15,6 +15,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import org.json.JSONObject
import org.xmtp.android.library.Client
import org.xmtp.android.library.ClientOptions
import org.xmtp.android.library.Conversation
Expand All @@ -23,6 +24,7 @@ import org.xmtp.android.library.XMTPEnvironment
import org.xmtp.android.library.XMTPException
import org.xmtp.android.library.messages.EnvelopeBuilder
import org.xmtp.android.library.messages.InvitationV1ContextBuilder
import org.xmtp.android.library.messages.Pagination
import org.xmtp.android.library.messages.PrivateKeyBuilder
import org.xmtp.android.library.messages.Signature
import org.xmtp.android.library.push.XMTPPush
Expand Down Expand Up @@ -196,13 +198,54 @@ class XMTPModule : Module() {
}
}

AsyncFunction("loadMessages") { clientAddress: String, topics: List<String>, conversationIDs: List<String?>, limit: Int?, before: Long?, after: Long? ->
AsyncFunction("loadMessages") { clientAddress: String, topic: String, limit: Int?, before: Long?, after: Long? ->
logV("loadMessages")
val client = clients[clientAddress] ?: throw XMTPException("No client")
val conversation =
findConversation(
clientAddress = clientAddress,
topic = topic,
) ?: throw XMTPException("no conversation found for $topic")
val beforeDate = if (before != null) Date(before) else null
val afterDate = if (after != null) Date(after) else null

client.conversations.listBatchMessages(topics, limit, beforeDate, afterDate).map {
conversation.messages(limit = limit, before = beforeDate, after = afterDate)
.map {
EncodedMessageWrapper.encode(it)
}
}

AsyncFunction("loadBatchMessages") { clientAddress: String, topics: List<String> ->
logV("loadBatchMessages")
val client = clients[clientAddress] ?: throw XMTPException("No client")
val topicsList = mutableListOf<Pair<String, Pagination>>()
topics.forEach {
val jsonObj = JSONObject(it)
val topic = jsonObj.get("topic").toString()
var limit: Int? = null
var before: Long? = null
var after: Long? = null

try {
limit = jsonObj.get("limit").toString().toInt()
before = jsonObj.get("before").toString().toLong()
after = jsonObj.get("after").toString().toLong()
} catch (e: Exception) {
Log.e(
"XMTPModule",
"Pagination given incorrect information ${e.message}"
)
}

val page = Pagination(
limit = if (limit != null && limit > 0) limit else null,
before = if (before != null && before > 0) Date(before) else null,
after = if (after != null && after > 0) Date(after) else null
)

topicsList.add(Pair(topic, page))
}

client.conversations.listBatchMessages(topicsList).map {
EncodedMessageWrapper.encode(it)
}
}
Expand Down
10 changes: 5 additions & 5 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -409,15 +409,15 @@ PODS:
- GenericJSON (~> 2.0)
- Logging (~> 1.0.0)
- secp256k1.swift (~> 0.1)
- XMTP (0.3.6-alpha0):
- XMTP (0.4.0-alpha0):
- Connect-Swift
- GzipSwift
- web3.swift
- XMTPRust (= 0.3.0-beta0)
- XMTPReactNative (0.1.0):
- ExpoModulesCore
- MessagePacker
- XMTP (= 0.3.6-alpha0)
- XMTP (= 0.4.0-alpha0)
- XMTPRust (0.3.0-beta0)
- Yoga (1.14.0)

Expand Down Expand Up @@ -660,11 +660,11 @@ SPEC CHECKSUMS:
secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
SwiftProtobuf: 40bd808372cb8706108f22d28f8ab4a6b9bc6989
web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959
XMTP: 4d8249fdf9d7f1aa34c87b8197bd9f8d5e998084
XMTPReactNative: ca04ab05ac0b7ce96861bfe1f6701c8c0899c918
XMTP: 8bb7ef01ba2ed0db8bbf699164d2da22dd989a25
XMTPReactNative: c8f5e3f2d2f1e9102b9fd888047b34919be057d4
XMTPRust: 233518ed46fbe3ea9e3bc3035de9a620dba09ce5
Yoga: ba09b6b11e6139e3df8229238aa794205ca6a02a

PODFILE CHECKSUM: 522d88edc2d5fac4825e60a121c24abc18983367

COCOAPODS: 1.11.3
COCOAPODS: 1.12.1
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
07EAFA006B8245D59AE6CF63 /* [CP] Embed Pods Frameworks */,
94CA905D5A9B66E6F6A747C8 /* [CP] Copy Pods Resources */,
0F8BF66945EEB514F253516D /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -286,7 +286,7 @@
shellPath = /bin/sh;
shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" $PROJECT_ROOT ios relative | tail -n 1)\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
};
07EAFA006B8245D59AE6CF63 /* [CP] Embed Pods Frameworks */ = {
0F8BF66945EEB514F253516D /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
Expand Down
15 changes: 11 additions & 4 deletions example/src/ConversationListView.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import React, { useEffect, useState } from "react";
import { Text, ScrollView, RefreshControl } from "react-native";
import { Conversation } from "xmtp-react-native-sdk";
import { Conversation, Query } from "xmtp-react-native-sdk";

import HomeHeaderView from "./HomeHeaderView";
import { RootStackParamList } from "./HomeView";
Expand All @@ -20,9 +20,14 @@ export default function ConversationListView({
async function refreshConversations() {
const conversations = await client.conversations.list();
const allMessages = await client.listBatchMessages(
conversations.map((conversation) => conversation.topic),
conversations.map((conversation) => conversation.conversationID)
conversations.map(
(conversation) =>
({
contentTopic: conversation.topic,
} as Query)
)
);

setConversations(conversations);
setMessageCount(allMessages.length);
}
Expand Down Expand Up @@ -67,7 +72,9 @@ export default function ConversationListView({
}
>
<HomeHeaderView client={client} navigation={navigation} />
<Text style={{marginLeft: 12}}>{messageCount} messages in {conversations.length} conversations</Text>
<Text style={{ marginLeft: 12 }}>
{messageCount} messages in {conversations.length} conversations
</Text>

{conversations.map((conversation) => {
return (
Expand Down
56 changes: 55 additions & 1 deletion example/src/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { content } from "@xmtp/proto";

import { NumberCodec, TextCodec } from "./test_utils";
import * as XMTP from "../../src/index";
import { DecodedMessage } from "../../src/index";
import { DecodedMessage, Query } from "../../src/index";
import { CodecError } from "../../src/lib/CodecError";
import { CodecRegistry } from "../../src/lib/CodecRegistry";
import { randomBytes } from "crypto";
Expand Down Expand Up @@ -241,3 +241,57 @@ test("createFromKeyBundle throws error for non string value", async () => {
}
return false;
});

test("can list batch messages", async () => {
const textCodec = new TextCodec();
const registry = new CodecRegistry();
registry.register(textCodec);

try {
const id = textCodec.contentType.id();
const codec = registry.find(id);

const encodedContent = codec.encode("Hello world");

const data = content.EncodedContent.encode(encodedContent).finish();

const bob = await XMTP.Client.createRandom("local");
const alice = await XMTP.Client.createRandom("local");

if (bob.address === alice.address) {
throw new Error("bob and alice should be different");
}

const bobConversation = await bob.conversations.newConversation(
alice.address
);

const aliceConversation = (await alice.conversations.list())[0];
if (!aliceConversation) {
throw new Error("aliceConversation should exist");
}

await bobConversation.send(data);

const messages: DecodedMessage[] = await alice.listBatchMessages([
{
contentTopic: bobConversation.topic,
} as Query,
{
contentTopic: aliceConversation.topic,
} as Query,
]);

if (messages.length < 1) {
throw Error("No message");
}

const firstMessage = messages?.[0];

const decodedMessage = codec.decode(firstMessage.content);

return decodedMessage === "Hello world";
} catch (e) {
return false;
}
});
61 changes: 56 additions & 5 deletions ios/XMTPModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,22 +209,73 @@ public class XMTPModule: Module {
}
}

AsyncFunction("loadMessages") { (clientAddress: String, topics: [String], conversationIDs: [String?], limit: Int?, before: Double?, after: Double?) -> [[UInt8]] in
AsyncFunction("loadMessages") { (clientAddress: String, topic: String, limit: Int?, before: Double?, after: Double?) -> [[UInt8]] in
let beforeDate = before != nil ? Date(timeIntervalSince1970: TimeInterval(before!)/1000) : nil
let afterDate = after != nil ? Date(timeIntervalSince1970: TimeInterval(after!)/1000) : nil

guard let client = clients[clientAddress] else {
throw Error.noClient
}

let decodedMessages = try await client.conversations.listBatchMessages(
topics: topics,
limit: limit,
before: beforeDate,
guard let conversation = try await findConversation(clientAddress: clientAddress, topic: topic) else {
throw Error.conversationNotFound("no conversation found for \(topic)")
}

let decodedMessages = try await conversation.messages(
limit: limit,
before: beforeDate,
after: afterDate)

let messages = try decodedMessages.map { (msg) in try EncodedMessageWrapper.encode(msg) }

return messages
}

AsyncFunction("loadBatchMessages") { (clientAddress: String, topics: [String]) -> [[UInt8]] in
guard let client = clients[clientAddress] else {
throw Error.noClient
}

var topicsList: [String: Pagination?] = [:]
topics.forEach { topicJSON in
let jsonData = topicJSON.data(using: .utf8)!
guard let jsonObj = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
let topic = jsonObj["topic"] as? String else {
return // Skip this topic if it doesn't have valid JSON data or missing "topic" field
}

var limit: Int?
var before: Double?
var after: Double?

if let limitStr = jsonObj["limit"] as? String,
let limitInt = Int(limitStr) {
limit = limitInt
}

if let beforeStr = jsonObj["before"] as? String,
let beforeLong = TimeInterval(beforeStr) {
before = beforeLong
}

if let afterStr = jsonObj["after"] as? String,
let afterLong = TimeInterval(afterStr) {
after = afterLong
}

let page = Pagination(
limit: limit ?? nil,
before: before != nil && before! > 0 ? Date(timeIntervalSince1970: before!) : nil,
after: after != nil && after! > 0 ? Date(timeIntervalSince1970: after!) : nil
)

topicsList[topic] = page
}

let decodedMessages = try await client.conversations.listBatchMessages(topics: topicsList)

let messages = try decodedMessages.map { (msg) in try EncodedMessageWrapper.encode(msg) }

return messages
}

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.3.6-alpha0"
s.dependency "XMTP", "= 0.4.0-alpha0"
end
Loading

0 comments on commit d9f5e8a

Please sign in to comment.