Skip to content

Commit

Permalink
Merge pull request #142 from xmtp/np/pppp-v2
Browse files Browse the repository at this point in the history
Implement Persistent Preferences
  • Loading branch information
nplasterer authored Oct 26, 2023
2 parents c262034 + 409fc0a commit d9072c3
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 23 deletions.
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.6.5"
implementation "org.xmtp:android:0.6.7"
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 @@ -23,6 +23,7 @@ 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.ConsentState
import org.xmtp.android.library.Conversation
import org.xmtp.android.library.PreparedMessage
import org.xmtp.android.library.SendOptions
Expand Down Expand Up @@ -488,6 +489,43 @@ class XMTPModule : Module() {
val decodedMessage = conversation.decode(envelope)
DecodedMessageWrapper.encode(decodedMessage)
}

AsyncFunction("isAllowed") { clientAddress: String, address: String ->
logV("isAllowed")
val client = clients[clientAddress] ?: throw XMTPException("No client")
client.contacts.isAllowed(address)
}

Function("isBlocked") { clientAddress: String, address: String ->
logV("isBlocked")
val client = clients[clientAddress] ?: throw XMTPException("No client")
client.contacts.isBlocked(address)
}

AsyncFunction("blockContacts") { clientAddress: String, addresses: List<String> ->
val client = clients[clientAddress] ?: throw XMTPException("No client")
client.contacts.block(addresses)
}

AsyncFunction("allowContacts") { clientAddress: String, addresses: List<String> ->
val client = clients[clientAddress] ?: throw XMTPException("No client")
client.contacts.allow(addresses)
}

AsyncFunction("refreshConsentList") { clientAddress: String ->
val client = clients[clientAddress] ?: throw XMTPException("No client")
client.contacts.refreshConsentList()
}

AsyncFunction("conversationConsentState") { clientAddress: String, conversationTopic: String ->
val conversation = findConversation(clientAddress, conversationTopic)
?: throw XMTPException("no conversation found for $conversationTopic")
when (conversation.consentState()) {
ConsentState.ALLOWED -> "allowed"
ConsentState.BLOCKED -> "blocked"
ConsentState.UNKNOWN -> "unknown"
}
}
}

//
Expand Down
26 changes: 13 additions & 13 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
PODS:
- BigInt (5.0.0)
- boost (1.76.0)
- Connect-Swift (0.8.0):
- SwiftProtobuf (~> 1.23.0)
- Connect-Swift (0.9.0):
- SwiftProtobuf (~> 1.24.0)
- DoubleConversion (1.1.6)
- EXApplication (5.1.1):
- ExpoModulesCore
Expand Down Expand Up @@ -409,22 +409,22 @@ PODS:
- RNSVG (13.9.0):
- React-Core
- secp256k1.swift (0.1.4)
- SwiftProtobuf (1.23.0)
- SwiftProtobuf (1.24.0)
- web3.swift (1.6.0):
- BigInt (~> 5.0.0)
- GenericJSON (~> 2.0)
- Logging (~> 1.0.0)
- secp256k1.swift (~> 0.1)
- XMTP (0.6.0-alpha0):
- XMTP (0.6.3-alpha0):
- Connect-Swift
- GzipSwift
- web3.swift
- XMTPRust (= 0.3.5-beta0)
- XMTPRust (= 0.3.6-beta0)
- XMTPReactNative (0.1.0):
- ExpoModulesCore
- MessagePacker
- XMTP (= 0.6.0-alpha0)
- XMTPRust (0.3.5-beta0)
- XMTP (= 0.6.3-alpha0)
- XMTPRust (0.3.6-beta0)
- Yoga (1.14.0)

DEPENDENCIES:
Expand Down Expand Up @@ -617,7 +617,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
BigInt: 74b4d88367b0e819d9f77393549226d36faeb0d8
boost: 57d2868c099736d80fcd648bf211b4431e51a558
Connect-Swift: ed4000df7d6cdd794f2530e844e649a672d032ee
Connect-Swift: 1d8dcd7b25e8c110f262b88b5c9eb8849d05e56c
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903
EXConstants: f348da07e21b23d2b085e270d7b74f282df1a7d9
Expand Down Expand Up @@ -678,13 +678,13 @@ SPEC CHECKSUMS:
RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315
secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
SwiftProtobuf: b70d65f419fbfe61a2d58003456ca5da58e337d6
SwiftProtobuf: bcfd2bc231cf9ae552cdc7c4e877bd3b41fe57b1
web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959
XMTP: 12be6af818fa6ab3343ec95a7fb0ac772f2e6877
XMTPReactNative: b2378747146547c703be3b4bba186bbf03f392d0
XMTPRust: e555ea9eb92b7575a522508533c00851b38dd316
XMTP: 3181e5c8e9d9885a97e5c5b22ac523bf9a713656
XMTPReactNative: 27185719aae360bc307cc6faf7bcf879cf681d97
XMTPRust: 3c958736a4f4ee798e425b5644551f1c948da4b0
Yoga: 065f0b74dba4832d6e328238de46eb72c5de9556

PODFILE CHECKSUM: 522d88edc2d5fac4825e60a121c24abc18983367

COCOAPODS: 1.12.1
COCOAPODS: 1.13.0
24 changes: 20 additions & 4 deletions example/src/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
Text,
View,
} from "react-native";
import React, { useContext } from "react";
import { Conversation } from "xmtp-react-native-sdk";
import React, { useContext, useState } from "react";
import { Conversation, Client } from "xmtp-react-native-sdk";
import { NavigationContext } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack/src/types";
import moment from "moment";
Expand All @@ -33,7 +33,7 @@ export default function HomeScreen() {
data={conversations || []}
keyExtractor={(item) => item.topic}
renderItem={({ item: conversation }) => (
<ConversationItem conversation={conversation} />
<ConversationItem conversation={conversation} client={client}/>
)}
ListHeaderComponent={
<View
Expand All @@ -57,10 +57,17 @@ export default function HomeScreen() {
);
}

function ConversationItem({ conversation }: { conversation: Conversation }) {
function ConversationItem({ conversation, client }: { conversation: Conversation, client: Client | null }) {
const navigation = useContext(NavigationContext);
const { data: messages } = useMessages({ topic: conversation.topic });
const lastMessage = messages?.[0];
let [getConsentState, setConsentState] = useState<string | undefined>();

conversation.consentState().then(result => {
setConsentState(result);
})
const blockContact = () => client?.contacts.block([conversation.peerAddress]);

return (
<Pressable
onPress={() =>
Expand All @@ -79,13 +86,22 @@ function ConversationItem({ conversation }: { conversation: Conversation }) {
<Text style={{ fontWeight: "bold" }}>
({messages?.length} messages)
</Text>
<Button
title="Block"
onPress={blockContact}
disabled={
getConsentState == "blocked"
}
/>
</View>
<View style={{ padding: 4 }}>
<Text numberOfLines={1} ellipsizeMode="tail">
{lastMessage?.content.text}
</Text>
<Text>{lastMessage?.senderAddress}:</Text>
<Text>{moment(lastMessage?.sent).fromNow()}</Text>
<Text style={{ fontWeight: "bold", color: "red" }}>{getConsentState}</Text>

</View>
</View>
</Pressable>
Expand Down
1 change: 1 addition & 0 deletions example/src/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { downloadFile, uploadFile } from "./storage";
*/
export function useConversationList(): UseQueryResult<Conversation[]> {
const { client } = useXmtp();
client?.contacts.refreshConsentList();
return useQuery<Conversation[]>(
["xmtp", "conversations", client?.address],
() => client!.conversations.list(),
Expand Down
47 changes: 43 additions & 4 deletions example/src/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ function test(name: string, perform: () => Promise<boolean>) {
tests.push({ name, run: perform });
}

// test("can fail", async () => {
// return false;
// });

test("can make a client", async () => {
const client = await XMTP.Client.createRandom({
env: "local",
Expand Down Expand Up @@ -549,3 +545,46 @@ test("can stream all messages", async () => {
return true;
});

test("canManagePreferences", async () => {
const bo = await XMTP.Client.createRandom({ env: "local" });
const alix = await XMTP.Client.createRandom({ env: "local" });
await delayToPropogate();

const alixConversation = await bo.conversations.newConversation(
alix.address,
);
await delayToPropogate();

const initialConvoState = await alixConversation.consentState();
if (initialConvoState != "allowed") {
throw new Error(`conversations created by bo should be allowed by default not ${initialConvoState}`);
}

const initialState = await bo.contacts.isAllowed(alixConversation.peerAddress);
if (!initialState) {
throw new Error(`contacts created by bo should be allowed by default not ${initialState}`);
}

bo.contacts.block([alixConversation.peerAddress]);
await delayToPropogate();

const blockedState = await bo.contacts.isBlocked(alixConversation.peerAddress);
const allowedState = await bo.contacts.isAllowed(alixConversation.peerAddress);
if (!blockedState) {
throw new Error(`contacts blocked by bo should be blocked not ${blockedState}`);
}

if (allowedState) {
throw new Error(`contacts blocked by bo should be blocked not ${allowedState}`);
}

const convoState = await alixConversation.consentState();
await delayToPropogate();

if (convoState != "blocked") {
throw new Error(`conversations blocked by bo should be blocked not ${convoState}`);
}

return true
});

46 changes: 46 additions & 0 deletions ios/XMTPModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,52 @@ public class XMTPModule: Module {
let decodedMessage = try conversation.decode(envelope)
return try DecodedMessageWrapper.encode(decodedMessage)
}

AsyncFunction("isAllowed") { (clientAddress: String, address: String) -> Bool in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
}
return await client.contacts.isAllowed(address)
}

AsyncFunction("isBlocked") { (clientAddress: String, address: String) -> Bool in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
}
return await client.contacts.isBlocked(address)
}

AsyncFunction("blockContacts") { (clientAddress: String, addresses: [String]) in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
}
try await client.contacts.block(addresses: addresses)
}

AsyncFunction("allowContacts") { (clientAddress: String, addresses: [String]) in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
}
try await client.contacts.allow(addresses: addresses)
}

AsyncFunction("refreshConsentList") { (clientAddress: String) in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
}
try await client.contacts.refreshConsentList()
}

AsyncFunction("conversationConsentState") { (clientAddress: String, conversationTopic: String) -> String in
guard let conversation = try await findConversation(clientAddress: clientAddress, topic: conversationTopic) else {
throw Error.conversationNotFound(conversationTopic)
}
switch (await conversation.consentState()) {
case .allowed: return "allowed"
case .blocked: return "blocked"
case .unknown: return "unknown"
}
}
}

//
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.6.0-alpha0"
s.dependency "XMTP", "= 0.6.3-alpha0"
end
41 changes: 41 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,47 @@ export async function decodeMessage(
);
}

export async function conversationConsentState(
clientAddress: string,
conversationTopic: string
): Promise<"allowed" | "blocked" | "unknown"> {
return await XMTPModule.conversationConsentState(clientAddress, conversationTopic);
}

export async function isAllowed(
clientAddress: string,
address: string,
): Promise<boolean> {
return await XMTPModule.isAllowed(clientAddress, address);
}

export async function isBlocked(
clientAddress: string,
address: string,
): Promise<boolean> {
return await XMTPModule.isBlocked(clientAddress, address);
}

export function blockContacts(
clientAddress: string,
addresses: string[],
) {
XMTPModule.blockContacts(clientAddress, addresses);
}

export function allowContacts(
clientAddress: string,
addresses: string[],
) {
XMTPModule.allowContacts(clientAddress, addresses);
}

export function refreshConsentList(
clientAddress: string
) {
XMTPModule.refreshConsentList(clientAddress);
}

export const emitter = new EventEmitter(XMTPModule ?? NativeModulesProxy.XMTP);

export { Client } from "./lib/Client";
Expand Down
3 changes: 3 additions & 0 deletions src/lib/Client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Signer, utils } from "ethers";

import Conversations from "./Conversations";
import Contacts from "./Contacts";
import type {
DecryptedLocalAttachment,
DecodedMessage,
Expand All @@ -15,6 +16,7 @@ declare const Buffer;
export class Client {
address: string;
conversations: Conversations;
contacts: Contacts;

static async create(
signer: Signer,
Expand Down Expand Up @@ -84,6 +86,7 @@ export class Client {
constructor(address: string) {
this.address = address;
this.conversations = new Conversations(this);
this.contacts = new Contacts(this);
}

async exportKeyBundle(): Promise<string> {
Expand Down
Loading

0 comments on commit d9072c3

Please sign in to comment.