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

Implement Persistent Preferences #142

Merged
merged 15 commits into from
Oct 26, 2023
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.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
Loading