diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 9aef8223a..b7479673d 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -172,14 +172,12 @@ class XMTPModule : Module() { dbEncryptionKey.foldIndexed(ByteArray(dbEncryptionKey.size)) { i, a, v -> a.apply { set(i, v.toByte()) } } - val historySyncUrl = authOptions.historySyncUrl ?: when (authOptions.environment) { "production" -> "https://message-history.production.ephemera.network/" "local" -> "http://0.0.0.0:5558" else -> "https://message-history.dev.ephemera.network/" } - return ClientOptions( api = apiEnvironments(authOptions.environment, authOptions.appVersion), preAuthenticateToInboxCallback = preAuthenticateToInboxCallback, @@ -533,9 +531,9 @@ class XMTPModule : Module() { } } - AsyncFunction("findDm") Coroutine { inboxId: String, peerAddress: String -> + AsyncFunction("findDmByAddress") Coroutine { inboxId: String, peerAddress: String -> withContext(Dispatchers.IO) { - logV("findDm") + logV("findDmByAddress") val client = clients[inboxId] ?: throw XMTPException("No client") val dm = client.findDm(peerAddress) dm?.let { diff --git a/example/src/LaunchScreen.tsx b/example/src/LaunchScreen.tsx index 8e9ac0f1a..6038d2d1f 100644 --- a/example/src/LaunchScreen.tsx +++ b/example/src/LaunchScreen.tsx @@ -173,16 +173,6 @@ export default function LaunchScreen( } /> - - Enable Groups: - - External Wallet: diff --git a/example/src/TestScreen.tsx b/example/src/TestScreen.tsx index 275bded58..f8eded841 100644 --- a/example/src/TestScreen.tsx +++ b/example/src/TestScreen.tsx @@ -2,6 +2,7 @@ import { useRoute } from '@react-navigation/native' import React, { useEffect, useState } from 'react' import { View, Text, Button, ScrollView } from 'react-native' +import { clientTests } from './tests/clientTests' import { conversationTests } from './tests/conversationTests' import { dmTests } from './tests/dmTests' import { groupPerformanceTests } from './tests/groupPerformanceTests' @@ -106,9 +107,10 @@ function TestView({ export enum TestCategory { all = 'all', - conversation = 'conversation', - group = 'group', + client = 'client', dm = 'dm', + group = 'group', + conversation = 'conversation', restartStreans = 'restartStreams', groupPermissions = 'groupPermissions', groupPerformance = 'groupPerformance', @@ -121,6 +123,7 @@ export default function TestScreen(): JSX.Element { testSelection: TestCategory } const allTests = [ + ...clientTests, ...dmTests, ...groupTests, ...conversationTests, @@ -133,6 +136,10 @@ export default function TestScreen(): JSX.Element { activeTests = allTests title = 'All Unit Tests' break + case TestCategory.client: + activeTests = clientTests + title = 'Client Unit Tests' + break case TestCategory.dm: activeTests = dmTests title = 'Dm Unit Tests' diff --git a/example/src/tests/clientTests.ts b/example/src/tests/clientTests.ts new file mode 100644 index 000000000..a88831b3f --- /dev/null +++ b/example/src/tests/clientTests.ts @@ -0,0 +1,283 @@ +import { Test, assert, createClients, delayToPropogate } from './test-utils' +import { Client, Conversation, ConversationId, ConversationVersion } from '../../../src/index' +import { Wallet } from 'ethers' +import RNFS from 'react-native-fs' + +export const clientTests: Test[] = [] +let counter = 1 +function test(name: string, perform: () => Promise) { + clientTests.push({ + name: String(counter++) + '. ' + name, + run: perform, + }) +} + + +test('can make a client', async () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const keyBytes = new Uint8Array([ + 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, + 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, + ]) + const client = await Client.createRandom({ + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: keyBytes, + }) + + const inboxId = await Client.getOrCreateInboxId(client.address, 'local') + + assert( + client.inboxId === inboxId, + `inboxIds should match but were ${client.inboxId} and ${inboxId}` + ) + return true +}) + +test('can revoke all other installations', async () => { + const keyBytes = new Uint8Array([ + 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, + 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, + ]) + const alixWallet = Wallet.createRandom() + + // create a v3 client + const alix = await Client.create(alixWallet, { + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: keyBytes, + }) + + await alix.deleteLocalDatabase() + + const alix2 = await Client.create(alixWallet, { + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: keyBytes, + }) + + const alix2Build = await Client.build(alix2.address, { + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: keyBytes, + }) + + await alix2.deleteLocalDatabase() + + const alix3 = await Client.create(alixWallet, { + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: keyBytes, + }) + + const inboxState2 = await alix3.inboxState(true) + assert( + inboxState2.installations.length === 3, + `installations length should be 3 but was ${inboxState2.installations.length}` + ) + + await alix3.revokeAllOtherInstallations(alixWallet) + + const inboxState3 = await alix3.inboxState(true) + assert( + inboxState3.installations.length === 1, + `installations length should be 1 but was ${inboxState3.installations.length}` + ) + + assert( + inboxState3.installations[0].createdAt !== undefined, + `installations createdAt should not be undefined` + ) + return true +}) + +test('calls preAuthenticateToInboxCallback when supplied', async () => { + let isCallbackCalled = 0 + let isPreAuthCalled = false + const preAuthenticateToInboxCallback = () => { + isCallbackCalled++ + isPreAuthCalled = true + } + + const keyBytes = new Uint8Array([ + 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, + 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, + ]) + + await Client.createRandom({ + env: 'local', + preAuthenticateToInboxCallback, + dbEncryptionKey: keyBytes, + }) + + assert( + isCallbackCalled === 1, + `callback should be called 1 times but was ${isCallbackCalled}` + ) + + if (!isPreAuthCalled) { + throw new Error('preAuthenticateToInboxCallback not called') + } + + return true +}) + +test('can delete a local database', async () => { + let [client, anotherClient] = await createClients(2) + + await client.conversations.newGroup([anotherClient.address]) + await client.conversations.sync() + assert( + (await client.conversations.listGroups()).length === 1, + `should have a group size of 1 but was ${ + (await client.conversations.listGroups()).length + }` + ) + + assert( + client.dbPath !== '', + `client dbPath should be set but was ${client.dbPath}` + ) + await client.deleteLocalDatabase() + client = await Client.createRandom({ + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: new Uint8Array([ + 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, + 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, + 145, + ]), + }) + await client.conversations.sync() + assert( + (await client.conversations.listGroups()).length === 0, + `should have a group size of 0 but was ${ + (await client.conversations.listGroups()).length + }` + ) + + return true +}) + +test('can make a client with encryption key and database directory', async () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db` + const directoryExists = await RNFS.exists(dbDirPath) + if (!directoryExists) { + await RNFS.mkdir(dbDirPath) + } + const key = new Uint8Array([ + 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, + 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, + ]) + + const client = await Client.createRandom({ + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: key, + dbDirectory: dbDirPath, + }) + + const anotherClient = await Client.createRandom({ + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: key, + }) + + await client.conversations.newGroup([anotherClient.address]) + assert( + (await client.conversations.listGroups()).length === 1, + `should have a group size of 1 but was ${ + (await client.conversations.listGroups()).length + }` + ) + + const clientFromBundle = await Client.build(client.address, { + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: key, + dbDirectory: dbDirPath, + }) + + assert( + clientFromBundle.address === client.address, + `clients dont match ${client.address} and ${clientFromBundle.address}` + ) + + assert( + (await clientFromBundle.conversations.listGroups()).length === 1, + `should have a group size of 1 but was ${ + (await clientFromBundle.conversations.listGroups()).length + }` + ) + return true +}) + +test('can drop a local database', async () => { + const [client, anotherClient] = await createClients(2) + + const group = await client.conversations.newGroup([anotherClient.address]) + await client.conversations.sync() + assert( + (await client.conversations.listGroups()).length === 1, + `should have a group size of 1 but was ${ + (await client.conversations.listGroups()).length + }` + ) + + await client.dropLocalDatabaseConnection() + + try { + await group.send('hi') + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + await client.reconnectLocalDatabase() + await group.send('hi') + return true + } + throw new Error('should throw when local database not connected') +}) + +test('can drop client from memory', async () => { + const [client, anotherClient] = await createClients(2) + await client.dropLocalDatabaseConnection() + await anotherClient.dropLocalDatabaseConnection() + + await client.reconnectLocalDatabase() + await Client.dropClient(anotherClient.inboxId) + try { + await anotherClient.reconnectLocalDatabase() + return false + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + // We cannot reconnect anotherClient because it was successfully dropped + return true + } +}) + +test('can get a inboxId from an address', async () => { + const [alix, bo] = await createClients(2) + + const boInboxId = await alix.findInboxIdFromAddress(bo.address) + assert(boInboxId === bo.inboxId, `${boInboxId} should match ${bo.inboxId}`) + return true +}) + +test('production client creation does not error', async () => { + const key = new Uint8Array([ + 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, + 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, + ]) + + try { + await Client.createRandom({ + env: 'production', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: key, + }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + throw error + } + return true +}) diff --git a/example/src/tests/conversationTests.ts b/example/src/tests/conversationTests.ts index 11b896e20..c9fdf552c 100644 --- a/example/src/tests/conversationTests.ts +++ b/example/src/tests/conversationTests.ts @@ -276,3 +276,139 @@ test('can stream all groups and conversations', async () => { return true }) + +test('can streamAll from multiple clients', async () => { + const [alix, bo, caro] = await createClients(3) + + // Setup stream alls + const allBoConversations: any[] = [] + const allAliConversations: any[] = [] + + await bo.conversations.stream(async (conversation) => { + allBoConversations.push(conversation) + }) + await alix.conversations.stream(async (conversation) => { + allAliConversations.push(conversation) + }) + + // Start Caro starts a new conversation. + await caro.conversations.newConversation(alix.address) + await delayToPropogate() + if (allBoConversations.length !== 0) { + throw Error( + 'Unexpected all conversations count for Bo ' + + allBoConversations.length + + ' and Alix had ' + + allAliConversations.length + ) + } + if (allAliConversations.length !== 1) { + throw Error( + 'Unexpected all conversations count ' + allAliConversations.length + ) + } + return true +}) + +test('can streamAll from multiple clients - swapped orderring', async () => { + const [alix, bo, caro] = await createClients(3) + + // Setup stream alls + const allBoConversations: any[] = [] + const allAliConversations: any[] = [] + + await alix.conversations.stream(async (conversation) => { + allAliConversations.push(conversation) + }) + + await bo.conversations.stream(async (conversation) => { + allBoConversations.push(conversation) + }) + + // Start Caro starts a new conversation. + await caro.conversations.newConversation(alix.address) + await delayToPropogate() + if (allBoConversations.length !== 0) { + throw Error( + 'Unexpected all conversations count for Bo ' + + allBoConversations.length + + ' and Alix had ' + + allAliConversations.length + ) + } + if (allAliConversations.length !== 1) { + throw Error( + 'Unexpected all conversations count ' + allAliConversations.length + ) + } + return true +}) + +test('can streamAllMessages from multiple clients', async () => { + const [alix, bo, caro] = await createClients(3) + + // Setup stream + const allBoMessages: any[] = [] + const allAliMessages: any[] = [] + + await bo.conversations.streamAllMessages(async (conversation) => { + allBoMessages.push(conversation) + }) + await alix.conversations.streamAllMessages(async (conversation) => { + allAliMessages.push(conversation) + }) + + // Start Caro starts a new conversation. + const caroConversation = await caro.conversations.newConversation( + alix.address + ) + await caroConversation.send({ text: `Message` }) + await delayToPropogate() + if (allBoMessages.length !== 0) { + throw Error('Unexpected all messages count for Bo ' + allBoMessages.length) + } + + if (allAliMessages.length !== 1) { + throw Error( + 'Unexpected all conversations count for Ali ' + allAliMessages.length + ) + } + + return true +}) + +test('can streamAllMessages from multiple clients - swapped', async () => { + const [alix, bo, caro] = await createClients(3) + + // Setup stream + const allBoMessages: any[] = [] + const allAliMessages: any[] = [] + const caroGroup = await caro.conversations.newGroup([alix.address]) + + await alix.conversations.streamAllMessages(async (conversation) => { + allAliMessages.push(conversation) + }) + await bo.conversations.streamAllMessages(async (conversation) => { + allBoMessages.push(conversation) + }) + + // Start Caro starts a new conversation. + const caroConvo = await caro.conversations.newConversation(alix.address) + await delayToPropogate() + await caroConvo.send({ text: `Message` }) + await caroGroup.send({ text: `Message` }) + await delayToPropogate() + if (allBoMessages.length !== 0) { + throw Error( + 'Unexpected all conversations count for Bo ' + allBoMessages.length + ) + } + + if (allAliMessages.length !== 2) { + throw Error( + 'Unexpected all conversations count for Ali ' + allAliMessages.length + ) + } + + return true +}) diff --git a/example/src/tests/createdAtTests.ts b/example/src/tests/createdAtTests.ts index cdcbed6b8..b1e7573c6 100644 --- a/example/src/tests/createdAtTests.ts +++ b/example/src/tests/createdAtTests.ts @@ -27,7 +27,7 @@ test('group createdAt matches listGroups', async () => { const boGroup = await bo.conversations.newGroup([alix.address]) // Fetch groups using listGroups method - await alix.conversations.syncConversations() + await alix.conversations.sync() const alixGroups = await alix.conversations.listGroups() const first = 0 @@ -73,7 +73,7 @@ test('group createdAt matches listAll', async () => { const boGroup = await bo.conversations.newGroup([alix.address]) // Fetch groups using listGroups method - await alix.conversations.syncConversations() + await alix.conversations.sync() const alixGroups = await alix.conversations.list() assert(alixGroups.length === 2, 'alix should have two groups') diff --git a/example/src/tests/groupPerformanceTests.ts b/example/src/tests/groupPerformanceTests.ts index cc70d39f4..92a2fceac 100644 --- a/example/src/tests/groupPerformanceTests.ts +++ b/example/src/tests/groupPerformanceTests.ts @@ -82,7 +82,7 @@ async function beforeAll( test('test compare V3 dms', async () => { await beforeAll(0, 0, 50, true) let start = Date.now() - await alixClient.conversations.syncConversations() + await alixClient.conversations.sync() let end = Date.now() console.log(`Davon synced ${50} Dms in ${end - start}ms`) @@ -94,7 +94,7 @@ test('test compare V3 dms', async () => { await createDms(alixClient, await createClients(5), 1) start = Date.now() - await alixClient.conversations.syncConversations() + await alixClient.conversations.sync() end = Date.now() console.log(`Davon synced ${dms.length} Dms in ${end - start}ms`) @@ -140,7 +140,7 @@ test('testing large group listings with ordering', async () => { ) start = Date.now() - await alixClient.conversations.syncConversations() + await alixClient.conversations.sync() end = Date.now() console.log(`Alix synced ${groups.length} groups in ${end - start}ms`) assert( @@ -149,7 +149,7 @@ test('testing large group listings with ordering', async () => { ) start = Date.now() - await boClient.conversations.syncConversations() + await boClient.conversations.sync() end = Date.now() console.log(`Bo synced ${groups.length} groups in ${end - start}ms`) @@ -201,7 +201,7 @@ test('testing large group listings', async () => { ) start = Date.now() - await alixClient.conversations.syncConversations() + await alixClient.conversations.sync() end = Date.now() console.log(`Alix synced ${groups.length} groups in ${end - start}ms`) assert( @@ -210,7 +210,7 @@ test('testing large group listings', async () => { ) start = Date.now() - await boClient.conversations.syncConversations() + await boClient.conversations.sync() end = Date.now() console.log(`Bo synced ${groups.length} groups in ${end - start}ms`) assert( @@ -252,7 +252,7 @@ test('testing large message listings', async () => { 'syncing 2000 self messages should take less than a .1 second' ) - await boClient.conversations.syncConversations() + await boClient.conversations.sync() const boGroup = await boClient.conversations.findGroup(alixGroup.id) start = Date.now() await boGroup!.sync() @@ -297,7 +297,7 @@ test('testing large member listings', async () => { 'syncing 50 members should take less than a .1 second' ) - await boClient.conversations.syncConversations() + await boClient.conversations.sync() const boGroup = await boClient.conversations.findGroup(alixGroup.id) start = Date.now() await boGroup!.sync() @@ -368,7 +368,7 @@ test('testing sending message in large group', async () => { 'sending a message should take less than a .2 second' ) - await boClient.conversations.syncConversations() + await boClient.conversations.sync() const boGroup = await boClient.conversations.findGroup(alixGroup.id) start = Date.now() await boGroup!.prepareMessage({ text: `Bo message` }) diff --git a/example/src/tests/groupPermissionsTests.ts b/example/src/tests/groupPermissionsTests.ts index 7522f980f..b3fa476c7 100644 --- a/example/src/tests/groupPermissionsTests.ts +++ b/example/src/tests/groupPermissionsTests.ts @@ -55,7 +55,7 @@ test('super admin can add a new admin', async () => { assert(!boIsSuperAdmin, `bo should not be a super admin`) // Verify that bo can not add a new admin - await bo.conversations.syncConversations() + await bo.conversations.sync() const boGroup = (await bo.conversations.listGroups())[0] try { await boGroup.addAdmin(caro.inboxId) @@ -67,7 +67,7 @@ test('super admin can add a new admin', async () => { // Alix adds bo as an admin await alixGroup.addAdmin(bo.inboxId) - await alix.conversations.syncConversations() + await alix.conversations.sync() const alixGroupIsAdmin = await alixGroup.isAdmin(bo.inboxId) assert(alixGroupIsAdmin, `alix should be an admin`) @@ -98,7 +98,7 @@ test('in admin only group, members can not update group name unless they are an ) // Verify that bo can not update the group name - await bo.conversations.syncConversations() + await bo.conversations.sync() const boGroup = (await bo.conversations.listGroups())[0] try { await boGroup.updateGroupName("bo's group") @@ -135,7 +135,7 @@ test('in admin only group, members can update group name once they are an admin' ) // Verify that bo can not update the group name - await bo.conversations.syncConversations() + await bo.conversations.sync() const boGroup = (await bo.conversations.listGroups())[0] try { await boGroup.updateGroupName("bo's group") @@ -147,7 +147,7 @@ test('in admin only group, members can update group name once they are an admin' // Alix adds bo as an admin await alixGroup.addAdmin(bo.inboxId) - await alix.conversations.syncConversations() + await alix.conversations.sync() const alixGroupIsAdmin = await alixGroup.isAdmin(bo.inboxId) assert(alixGroupIsAdmin, `alix should be an admin`) @@ -190,12 +190,12 @@ test('in admin only group, members can not update group name after admin status // Alix adds bo as an admin await alixGroup.addAdmin(bo.inboxId) - await alix.conversations.syncConversations() + await alix.conversations.sync() let boIsAdmin = await alixGroup.isAdmin(bo.inboxId) assert(boIsAdmin, `bo should be an admin`) // Now bo can update the group name - await bo.conversations.syncConversations() + await bo.conversations.sync() const boGroup = (await bo.conversations.listGroups())[0] await boGroup.sync() await boGroup.updateGroupName("bo's group") @@ -208,7 +208,7 @@ test('in admin only group, members can not update group name after admin status // Now alix removed bo as an admin await alixGroup.removeAdmin(bo.inboxId) - await alix.conversations.syncConversations() + await alix.conversations.sync() boIsAdmin = await alixGroup.isAdmin(bo.inboxId) assert(!boIsAdmin, `bo should not be an admin`) @@ -251,7 +251,7 @@ test('can not remove a super admin from a group', async () => { `number of members should be 2 but was ${numMembers}` ) - await bo.conversations.syncConversations() + await bo.conversations.sync() const boGroup = (await bo.conversations.listGroups())[0] await boGroup.sync() @@ -327,7 +327,7 @@ test('can commit after invalid permissions commit', async () => { [alix.address, caro.address], { permissionLevel: 'all_members' } ) - await alix.conversations.syncConversations() + await alix.conversations.sync() const alixGroup = (await alix.conversations.listGroups())[0] // Verify that Alix cannot add an admin @@ -373,7 +373,7 @@ test('group with All Members policy has remove function that is admin only', asy [alix.address, caro.address], { permissionLevel: 'all_members' } ) - await alix.conversations.syncConversations() + await alix.conversations.sync() const alixGroup = (await alix.conversations.listGroups())[0] // Verify that Alix cannot remove a member @@ -429,7 +429,7 @@ test('can update group permissions', async () => { ) // Verify that alix can not update the group description - await alix.conversations.syncConversations() + await alix.conversations.sync() const alixGroup = (await alix.conversations.listGroups())[0] try { await alixGroup.updateGroupDescription('new description 2') @@ -479,7 +479,7 @@ test('can update group pinned frame', async () => { ) // Verify that alix can not update the group pinned frame - await alix.conversations.syncConversations() + await alix.conversations.sync() const alixGroup = (await alix.conversations.listGroups())[0] try { await alixGroup.updateGroupPinnedFrameUrl('new pinned frame') @@ -540,7 +540,7 @@ test('can create a group with custom permissions', async () => { ) // Verify that bo can read the correct permissions - await alix.conversations.syncConversations() + await alix.conversations.sync() const alixGroup = (await alix.conversations.listGroups())[0] const permissions = await alixGroup.permissionPolicySet() assert( diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index 6cb5d4d77..b5f37cd51 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -1,5 +1,4 @@ import { Wallet } from 'ethers' -import RNFS from 'react-native-fs' import { DecodedMessage } from 'xmtp-react-native-sdk/lib/DecodedMessage' import { @@ -13,7 +12,6 @@ import { Client, Conversation, Group, - ConversationVersion, GroupUpdatedContent, GroupUpdatedCodec, ConsentListEntry, @@ -25,283 +23,6 @@ function test(name: string, perform: () => Promise) { groupTests.push({ name: String(counter++) + '. ' + name, run: perform }) } -test('can make a MLS V3 client', async () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const keyBytes = new Uint8Array([ - 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, - 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, - ]) - const client = await Client.createRandom({ - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: keyBytes, - }) - - const inboxId = await Client.getOrCreateInboxId(client.address, 'local') - - assert( - client.inboxId === inboxId, - `inboxIds should match but were ${client.inboxId} and ${inboxId}` - ) - return true -}) - -test('can revoke all other installations', async () => { - const keyBytes = new Uint8Array([ - 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, - 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, - ]) - const alixWallet = Wallet.createRandom() - - // create a v3 client - const alix = await Client.create(alixWallet, { - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: keyBytes, - }) - - await alix.deleteLocalDatabase() - - const alix2 = await Client.create(alixWallet, { - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: keyBytes, - }) - - const alix2Build = await Client.build(alix2.address, { - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: keyBytes, - }) - - await alix2.deleteLocalDatabase() - - const alix3 = await Client.create(alixWallet, { - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: keyBytes, - }) - - const inboxState2 = await alix3.inboxState(true) - assert( - inboxState2.installations.length === 3, - `installations length should be 3 but was ${inboxState2.installations.length}` - ) - - await alix3.revokeAllOtherInstallations(alixWallet) - - const inboxState3 = await alix3.inboxState(true) - assert( - inboxState3.installations.length === 1, - `installations length should be 1 but was ${inboxState3.installations.length}` - ) - - assert( - inboxState3.installations[0].createdAt !== undefined, - `installations createdAt should not be undefined` - ) - return true -}) - -test('calls preAuthenticateToInboxCallback when supplied', async () => { - let isCallbackCalled = 0 - let isPreAuthCalled = false - const preAuthenticateToInboxCallback = () => { - isCallbackCalled++ - isPreAuthCalled = true - } - const preEnableIdentityCallback = () => { - isCallbackCalled++ - } - const preCreateIdentityCallback = () => { - isCallbackCalled++ - } - const keyBytes = new Uint8Array([ - 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, - 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, - ]) - - await Client.createRandom({ - env: 'local', - enableV3: true, - preEnableIdentityCallback, - preCreateIdentityCallback, - preAuthenticateToInboxCallback, - dbEncryptionKey: keyBytes, - }) - - assert( - isCallbackCalled === 3, - `callback should be called 3 times but was ${isCallbackCalled}` - ) - - if (!isPreAuthCalled) { - throw new Error('preAuthenticateToInboxCallback not called') - } - - return true -}) - -test('can delete a local database', async () => { - let [client, anotherClient] = await createClients(2) - - await client.conversations.newGroup([anotherClient.address]) - await client.conversations.syncConversations() - assert( - (await client.conversations.listGroups()).length === 1, - `should have a group size of 1 but was ${ - (await client.conversations.listGroups()).length - }` - ) - - assert( - client.dbPath !== '', - `client dbPath should be set but was ${client.dbPath}` - ) - await client.deleteLocalDatabase() - client = await Client.createRandom({ - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: new Uint8Array([ - 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, - 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, - 145, - ]), - }) - await client.conversations.syncConversations() - assert( - (await client.conversations.listGroups()).length === 0, - `should have a group size of 0 but was ${ - (await client.conversations.listGroups()).length - }` - ) - - return true -}) - -test('can make a MLS V3 client with encryption key and database directory', async () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db` - const directoryExists = await RNFS.exists(dbDirPath) - if (!directoryExists) { - await RNFS.mkdir(dbDirPath) - } - const key = new Uint8Array([ - 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, - 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, - ]) - - const client = await Client.createRandom({ - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: key, - dbDirectory: dbDirPath, - }) - - const anotherClient = await Client.createRandom({ - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: key, - }) - - await client.conversations.newGroup([anotherClient.address]) - assert( - (await client.conversations.listGroups()).length === 1, - `should have a group size of 1 but was ${ - (await client.conversations.listGroups()).length - }` - ) - - const clientFromBundle = await Client.build(client.address, { - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: key, - dbDirectory: dbDirPath, - }) - - assert( - clientFromBundle.address === client.address, - `clients dont match ${client.address} and ${clientFromBundle.address}` - ) - - assert( - (await clientFromBundle.conversations.listGroups()).length === 1, - `should have a group size of 1 but was ${ - (await clientFromBundle.conversations.listGroups()).length - }` - ) - return true -}) - -test('can drop a local database', async () => { - const [client, anotherClient] = await createClients(2) - - const group = await client.conversations.newGroup([anotherClient.address]) - await client.conversations.syncConversations() - assert( - (await client.conversations.listGroups()).length === 1, - `should have a group size of 1 but was ${ - (await client.conversations.listGroups()).length - }` - ) - - await client.dropLocalDatabaseConnection() - - try { - await group.send('hi') - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error) { - await client.reconnectLocalDatabase() - await group.send('hi') - return true - } - throw new Error('should throw when local database not connected') -}) - -test('can drop client from memory', async () => { - const [client, anotherClient] = await createClients(2) - await client.dropLocalDatabaseConnection() - await anotherClient.dropLocalDatabaseConnection() - - await client.reconnectLocalDatabase() - await Client.dropClient(anotherClient.inboxId) - try { - await anotherClient.reconnectLocalDatabase() - return false - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error) { - // We cannot reconnect anotherClient because it was successfully dropped - return true - } -}) - -test('can get a inboxId from an address', async () => { - const [alix, bo] = await createClients(2) - - const boInboxId = await alix.findInboxIdFromAddress(bo.address) - assert(boInboxId === bo.inboxId, `${boInboxId} should match ${bo.inboxId}`) - return true -}) - -test('production MLS V3 client creation does not error', async () => { - const key = new Uint8Array([ - 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, - 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, - ]) - - try { - await Client.createRandom({ - env: 'production', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: key, - }) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error) { - throw error - } - return true -}) - test('can cancel streams', async () => { const [alix, bo] = await createClients(2) let messageCallbacks = 0 @@ -376,7 +97,7 @@ test('group message delivery status', async () => { `the message should have a delivery status of PUBLISHED but was ${alixMessages2[0].deliveryStatus}` ) - await boClient.conversations.syncConversations() + await boClient.conversations.sync() const boGroup = (await boClient.conversations.listGroups())[0] await boGroup.sync() const boMessages: DecodedMessage[] = await boGroup.messages() @@ -398,7 +119,7 @@ test('can find a group by id', async () => { const [alixClient, boClient] = await createClients(2) const alixGroup = await alixClient.conversations.newGroup([boClient.address]) - await boClient.conversations.syncConversations() + await boClient.conversations.sync() const boGroup = await boClient.conversations.findGroup(alixGroup.id) assert( @@ -413,7 +134,7 @@ test('can find a message by id', async () => { const alixGroup = await alixClient.conversations.newGroup([boClient.address]) const alixMessageId = await alixGroup.send('Hello') - await boClient.conversations.syncConversations() + await boClient.conversations.sync() const boGroup = await boClient.conversations.findGroup(alixGroup.id) await boGroup?.sync() const boMessage = await boClient.conversations.findMessage(alixMessageId) @@ -429,7 +150,7 @@ test('who added me to a group', async () => { const [alixClient, boClient] = await createClients(2) await alixClient.conversations.newGroup([boClient.address]) - await boClient.conversations.syncConversations() + await boClient.conversations.sync() const boGroup = (await boClient.conversations.listGroups())[0] const addedByInboxId = await boGroup.addedByInboxId @@ -475,7 +196,6 @@ test('can get members of a group', async () => { }) test('can message in a group', async () => { - // Create three MLS enabled Clients const [alixClient, boClient, caroClient] = await createClients(3) // alix's num groups start at 0 @@ -491,7 +211,7 @@ test('can message in a group', async () => { ]) // alix's num groups == 1 - await alixClient.conversations.syncConversations() + await alixClient.conversations.sync() alixGroups = await alixClient.conversations.listGroups() if (alixGroups.length !== 1) { throw new Error('num groups should be 1') @@ -522,7 +242,7 @@ test('can message in a group', async () => { await alixGroup.send('gm') // bo's num groups == 1 - await boClient.conversations.syncConversations() + await boClient.conversations.sync() const boGroups = await boClient.conversations.listGroups() if (boGroups.length !== 1) { throw new Error( @@ -549,7 +269,7 @@ test('can message in a group', async () => { await boGroups[0].send('hey guys!') // caro's num groups == 1 - await caroClient.conversations.syncConversations() + await caroClient.conversations.sync() const caroGroups = await caroClient.conversations.listGroups() if (caroGroups.length !== 1) { throw new Error( @@ -584,19 +304,23 @@ test('unpublished messages handling', async () => { // Create a new group with Bob and Alice const boGroup = await boClient.conversations.newGroup([alixClient.address]) + assert( + (await boGroup.consentState()) === 'allowed', + 'consent should be allowed' + ) // Sync Alice's client to get the new group - await alixClient.conversations.syncConversations() + await alixClient.conversations.sync() const alixGroup = await alixClient.conversations.findGroup(boGroup.id) if (!alixGroup) { throw new Error(`Group not found for id: ${boGroup.id}`) } // Check if the group is allowed initially - let isGroupAllowed = await alixClient.preferences.conversationIdConsentState( + const alixGroupState = await alixClient.preferences.conversationConsentState( boGroup.id ) - if (isGroupAllowed !== 'allowed') { + if (alixGroupState !== 'unknown') { throw new Error('Group should not be allowed initially') } @@ -604,7 +328,7 @@ test('unpublished messages handling', async () => { const preparedMessageId = await alixGroup.prepareMessage('Test text') // Check if the group is allowed after preparing the message - isGroupAllowed = await alixClient.preferences.conversationIdConsentState( + const isGroupAllowed = await alixClient.preferences.conversationConsentState( boGroup.id ) if (isGroupAllowed === 'allowed') { @@ -662,7 +386,7 @@ test('can add members to a group', async () => { const alixGroup = await alixClient.conversations.newGroup([boClient.address]) // alix's num groups == 1 - await alixClient.conversations.syncConversations() + await alixClient.conversations.sync() alixGroups = await alixClient.conversations.listGroups() if (alixGroups.length !== 1) { throw new Error('num groups should be 1') @@ -688,7 +412,7 @@ test('can add members to a group', async () => { await alixGroup.send('gm') // bo's num groups == 1 - await boClient.conversations.syncConversations() + await boClient.conversations.sync() boGroups = await boClient.conversations.listGroups() if (boGroups.length !== 1) { throw new Error( @@ -699,7 +423,7 @@ test('can add members to a group', async () => { await alixGroup.addMembers([caroClient.address]) // caro's num groups == 1 - await caroClient.conversations.syncConversations() + await caroClient.conversations.sync() caroGroups = await caroClient.conversations.listGroups() if (caroGroups.length !== 1) { throw new Error( @@ -748,7 +472,7 @@ test('can remove members from a group', async () => { ]) // alix's num groups == 1 - await alixClient.conversations.syncConversations() + await alixClient.conversations.sync() alixGroups = await alixClient.conversations.listGroups() if (alixGroups.length !== 1) { throw new Error('num groups should be 1') @@ -774,7 +498,7 @@ test('can remove members from a group', async () => { await alixGroup.send('gm') // bo's num groups == 1 - await boClient.conversations.syncConversations() + await boClient.conversations.sync() boGroups = await boClient.conversations.listGroups() if (boGroups.length !== 1) { throw new Error( @@ -783,7 +507,7 @@ test('can remove members from a group', async () => { } // caro's num groups == 1 - await caroClient.conversations.syncConversations() + await caroClient.conversations.sync() caroGroups = await caroClient.conversations.listGroups() if (caroGroups.length !== 1) { throw new Error( @@ -888,7 +612,8 @@ test('can stream groups', async () => { const cancelstream = await alixClient.conversations.stream( async (group: Conversation) => { groups.push(group) - } + }, + 'groups' ) // caro creates a group with alix, so stream callback is fired @@ -912,7 +637,7 @@ test('can stream groups', async () => { } // * Note alix creating a group does not trigger alix conversations - // group stream. Workaround is to syncConversations after you create and list manually + // group stream. Workaround is to sync after you create and list manually // See https://github.com/xmtp/libxmtp/issues/504 // alix creates a group @@ -1017,7 +742,7 @@ test('can list groups', async () => { }) const boGroups = await boClient.conversations.listGroups() - await alixClient.conversations.syncConversations() + await alixClient.conversations.sync() const alixGroups = await alixClient.conversations.listGroups() assert( @@ -1056,91 +781,6 @@ test('can list groups', async () => { return true }) -test('can list all groups and conversations', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) - - // Add one group and one conversation - const boGroup = await boClient.conversations.newGroup([alixClient.address]) - const alixConversation = await alixClient.conversations.newConversation( - caroClient.address - ) - - const listedContainers = await alixClient.conversations.list() - - // Verify information in listed containers is correct - // BUG - List All returns in Chronological order on iOS - // and reverse Chronological order on Android - const first = 0 - const second = 1 - if ( - listedContainers[first].topic !== boGroup.topic || - listedContainers[first].version !== ConversationVersion.GROUP || - listedContainers[second].createdAt !== alixConversation.createdAt - ) { - throw Error('Listed containers should match streamed containers') - } - - return true -}) - -test('can stream all groups and conversations', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) - - // Start streaming groups and conversations - const containers: Conversation[] = [] - const cancelStream = await alixClient.conversations.stream( - async (Conversation: Conversation) => { - containers.push(Conversation) - } - ) - - // bo creates a group with alix, so stream callback is fired - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const boGroup = await boClient.conversations.newGroup([alixClient.address]) - await delayToPropogate() - if ((containers.length as number) !== 1) { - throw Error('Unexpected num groups (should be 1): ' + containers.length) - } - - // bo creates a v2 Conversation with alix so a stream callback is fired - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const boConversation = await boClient.conversations.newConversation( - alixClient.address - ) - await delayToPropogate() - if ((containers.length as number) !== 2) { - throw Error('Unexpected num groups (should be 2): ' + containers.length) - } - - // * Note alix creating a v2 Conversation does trigger alix conversations - // stream. - - // alix creates a V2 Conversationgroup - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const alixConversation = await alixClient.conversations.newConversation( - caroClient.address - ) - await delayToPropogate() - if (containers.length !== 3) { - throw Error('Expected group length 3 but it is: ' + containers.length) - } - - cancelStream() - await delayToPropogate() - - // Creating a group should no longer trigger stream groups - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const caroConversation = await caroClient.conversations.newGroup([ - alixClient.address, - ]) - await delayToPropogate() - if ((containers.length as number) !== 3) { - throw Error('Unexpected num groups (should be 3): ' + containers.length) - } - - return true -}) - test('can stream groups and messages', async () => { const [alixClient, boClient] = await createClients(2) @@ -1210,7 +850,7 @@ test('can stream group messages', async () => { ) // bo's num groups == 1 - await boClient.conversations.syncConversations() + await boClient.conversations.sync() const boGroup = (await boClient.conversations.listGroups())[0] for (let i = 0; i < 5; i++) { @@ -1241,62 +881,6 @@ test('can stream group messages', async () => { return true }) -test('can stream all messages', async () => { - const [alix, bo, caro] = await createClients(3) - - await delayToPropogate() - - // Record message stream across all conversations - const allMessages: DecodedMessage[] = [] - await alix.conversations.streamAllMessages(async (message) => { - allMessages.push(message) - }) - - // Start bo starts a new conversation. - const boConvo = await bo.conversations.newConversation(alix.address) - await delayToPropogate() - - for (let i = 0; i < 5; i++) { - await boConvo.send({ text: `Message ${i}` }) - await delayToPropogate() - } - - const count = allMessages.length - if (count !== 5) { - throw Error('Unexpected all messages count ' + allMessages.length) - } - - const caroConvo = await caro.conversations.newConversation(alix.address) - const caroGroup = await caro.conversations.newGroup([alix.address]) - await delayToPropogate() - for (let i = 0; i < 5; i++) { - await caroConvo.send({ text: `Message ${i}` }) - await caroGroup.send({ text: `Message ${i}` }) - await delayToPropogate() - } - - if (allMessages.length !== 10) { - throw Error('Unexpected all messages count ' + allMessages.length) - } - - alix.conversations.cancelStreamAllMessages() - - await alix.conversations.streamAllMessages(async (message) => { - allMessages.push(message) - }) - - for (let i = 0; i < 5; i++) { - await boConvo.send({ text: `Message ${i}` }) - await caroGroup.send({ text: `Message ${i}` }) - await delayToPropogate() - } - if (allMessages.length <= 15) { - throw Error('Unexpected all messages count ' + allMessages.length) - } - - return true -}) - test('can make a group with metadata', async () => { const [alix, bo] = await createClients(2) bo.register(new GroupUpdatedCodec()) @@ -1329,7 +913,7 @@ test('can make a group with metadata', async () => { await alixGroup.updateGroupImageUrlSquare('newurl.com') await alixGroup.updateGroupDescription('a new group description') await alixGroup.sync() - await bo.conversations.syncConversations() + await bo.conversations.sync() const boGroups = await bo.conversations.listGroups() const boGroup = boGroups[0] await boGroup.sync() @@ -1425,7 +1009,7 @@ test('can paginate group messages', async () => { await alixGroup.send('hello, world') await alixGroup.send('gm') - await boClient.conversations.syncConversations() + await boClient.conversations.sync() const boGroups = await boClient.conversations.listGroups() if (boGroups.length !== 1) { throw new Error( @@ -1462,10 +1046,10 @@ test('can stream all group messages', async () => { const allMessages: DecodedMessage[] = [] // If we don't call syncConversations here, the streamAllGroupMessages will not // stream the first message. Feels like a bug. - await alix.conversations.syncConversations() + await alix.conversations.sync() await alix.conversations.streamAllMessages(async (message) => { allMessages.push(message) - }) + }, 'groups') for (let i = 0; i < 5; i++) { await boGroup.send({ text: `Message ${i}` }) @@ -1504,251 +1088,21 @@ test('can stream all group messages', async () => { return true }) -test('can streamAll from multiple clients', async () => { - const [alix, bo, caro] = await createClients(3) - - // Setup stream alls - const allBoConversations: any[] = [] - const allAliConversations: any[] = [] - - await bo.conversations.stream(async (conversation) => { - allBoConversations.push(conversation) - }) - await alix.conversations.stream(async (conversation) => { - allAliConversations.push(conversation) - }) - - // Start Caro starts a new conversation. - await caro.conversations.newConversation(alix.address) - await delayToPropogate() - if (allBoConversations.length !== 0) { - throw Error( - 'Unexpected all conversations count for Bo ' + - allBoConversations.length + - ' and Alix had ' + - allAliConversations.length - ) - } - if (allAliConversations.length !== 1) { - throw Error( - 'Unexpected all conversations count ' + allAliConversations.length - ) - } - return true -}) - -test('can streamAll from multiple clients - swapped orderring', async () => { - const [alix, bo, caro] = await createClients(3) - - // Setup stream alls - const allBoConversations: any[] = [] - const allAliConversations: any[] = [] - - await alix.conversations.stream(async (conversation) => { - allAliConversations.push(conversation) - }) - - await bo.conversations.stream(async (conversation) => { - allBoConversations.push(conversation) - }) - - // Start Caro starts a new conversation. - await caro.conversations.newConversation(alix.address) - await delayToPropogate() - if (allBoConversations.length !== 0) { - throw Error( - 'Unexpected all conversations count for Bo ' + - allBoConversations.length + - ' and Alix had ' + - allAliConversations.length - ) - } - if (allAliConversations.length !== 1) { - throw Error( - 'Unexpected all conversations count ' + allAliConversations.length - ) - } - return true -}) - -test('can streamAllMessages from multiple clients', async () => { - const [alix, bo, caro] = await createClients(3) - - // Setup stream - const allBoMessages: any[] = [] - const allAliMessages: any[] = [] - - await bo.conversations.streamAllMessages(async (conversation) => { - allBoMessages.push(conversation) - }) - await alix.conversations.streamAllMessages(async (conversation) => { - allAliMessages.push(conversation) - }) - - // Start Caro starts a new conversation. - const caroConversation = await caro.conversations.newConversation( - alix.address - ) - await caroConversation.send({ text: `Message` }) - await delayToPropogate() - if (allBoMessages.length !== 0) { - throw Error('Unexpected all messages count for Bo ' + allBoMessages.length) - } - - if (allAliMessages.length !== 1) { - throw Error( - 'Unexpected all conversations count for Ali ' + allAliMessages.length - ) - } - - return true -}) - -test('can streamAllMessages from multiple clients - swapped', async () => { - const [alix, bo, caro] = await createClients(3) - - // Setup stream - const allBoMessages: any[] = [] - const allAliMessages: any[] = [] - const caroGroup = await caro.conversations.newGroup([alix.address]) - - await alix.conversations.streamAllMessages(async (conversation) => { - allAliMessages.push(conversation) - }) - await bo.conversations.streamAllMessages(async (conversation) => { - allBoMessages.push(conversation) - }) - - // Start Caro starts a new conversation. - const caroConvo = await caro.conversations.newConversation(alix.address) - await delayToPropogate() - await caroConvo.send({ text: `Message` }) - await caroGroup.send({ text: `Message` }) - await delayToPropogate() - if (allBoMessages.length !== 0) { - throw Error( - 'Unexpected all conversations count for Bo ' + allBoMessages.length - ) - } - - if (allAliMessages.length !== 2) { - throw Error( - 'Unexpected all conversations count for Ali ' + allAliMessages.length - ) - } - - return true -}) - -test('can stream all group Messages from multiple clients', async () => { - const [alix, bo, caro] = await createClients(3) - - // Setup stream - const allAlixMessages: DecodedMessage[] = [] - const allBoMessages: DecodedMessage[] = [] - const alixGroup = await caro.conversations.newGroup([alix.address]) - const boGroup = await caro.conversations.newGroup([bo.address]) - - await alixGroup.streamMessages(async (message) => { - allAlixMessages.push(message) - }) - await boGroup.streamMessages(async (message) => { - allBoMessages.push(message) - }) - - // Start Caro starts a new conversation. - await delayToPropogate() - await alixGroup.send({ text: `Message` }) - await delayToPropogate() - if (allBoMessages.length !== 0) { - throw Error('Unexpected all messages count for Bo ' + allBoMessages.length) - } - - if (allAlixMessages.length !== 1) { - throw Error( - 'Unexpected all messages count for Ali ' + allAlixMessages.length - ) - } - - await alix.conversations.syncConversations() - const alixConv = (await alix.conversations.listGroups())[0] - await alixConv.send({ text: `Message` }) - await delayToPropogate() - if (allBoMessages.length !== 0) { - throw Error('Unexpected all messages count for Bo ' + allBoMessages.length) - } - // @ts-ignore-next-line - if (allAlixMessages.length !== 2) { - throw Error( - 'Unexpected all messages count for Ali ' + allAlixMessages.length - ) - } - - return true -}) - -test('can stream all group Messages from multiple clients - swapped', async () => { - const [alix, bo, caro] = await createClients(3) - - // Setup stream - const allAlixMessages: DecodedMessage[] = [] - const allBoMessages: DecodedMessage[] = [] - const alixGroup = await caro.conversations.newGroup([alix.address]) - const boGroup = await caro.conversations.newGroup([bo.address]) - - await boGroup.streamMessages(async (message) => { - allBoMessages.push(message) - }) - await alixGroup.streamMessages(async (message) => { - allAlixMessages.push(message) - }) - - // Start Caro starts a new conversation. - await delayToPropogate() - await alixGroup.send({ text: `Message` }) - await delayToPropogate() - if (allBoMessages.length !== 0) { - throw Error('Unexpected all messages count for Bo ' + allBoMessages.length) - } - - if (allAlixMessages.length !== 1) { - throw Error( - 'Unexpected all messages count for Ali ' + allAlixMessages.length - ) - } - - await alix.conversations.syncConversations() - const alixConv = (await alix.conversations.listGroups())[0] - await alixConv.send({ text: `Message` }) - await delayToPropogate() - if (allBoMessages.length !== 0) { - throw Error('Unexpected all messages count for Bo ' + allBoMessages.length) - } - // @ts-ignore-next-line - if (allAlixMessages.length !== 2) { - throw Error( - 'Unexpected all messages count for Ali ' + allAlixMessages.length - ) - } - - return true -}) - test('creating a group should allow group', async () => { const [alix, bo] = await createClients(2) const group = await alix.conversations.newGroup([bo.address]) - const consent = await alix.preferences.conversationIdConsentState(group.id) + await alix.conversations.sync() + const consent = await alix.preferences.conversationConsentState(group.id) const groupConsent = await group.consentState() - if (!consent || !groupConsent) { + if (consent !== groupConsent) { throw Error('Group should be allowed') } - const state = await group.consentState() assert( - state === 'allowed', - `the message should have a consent state of allowed but was ${state}` + groupConsent === 'allowed', + `the message should have a consent state of allowed but was ${groupConsent}` ) return true @@ -1757,13 +1111,14 @@ test('creating a group should allow group', async () => { test('can group consent', async () => { const [alix, bo] = await createClients(2) const group = await bo.conversations.newGroup([alix.address]) - let isAllowed = await alix.preferences.conversationIdConsentState(group.id) + await alix.conversations.sync() + let isAllowed = await alix.preferences.conversationConsentState(group.id) assert( isAllowed !== 'allowed', `alix group should NOT be allowed but was ${isAllowed}` ) - isAllowed = await bo.preferences.conversationIdConsentState(group.id) + isAllowed = await bo.preferences.conversationConsentState(group.id) assert( isAllowed === 'allowed', `bo group should be allowed but was ${isAllowed}` @@ -1776,7 +1131,7 @@ test('can group consent', async () => { await bo.preferences.setConsentState( new ConsentListEntry(group.id, 'group_id', 'denied') ) - const isDenied = await bo.preferences.conversationIdConsentState(group.id) + const isDenied = await bo.preferences.conversationConsentState(group.id) assert(isDenied === 'denied', `bo group should be denied but was ${isDenied}`) assert( (await group.consentState()) === 'denied', @@ -1784,7 +1139,7 @@ test('can group consent', async () => { ) await group.updateConsent('allowed') - isAllowed = await bo.preferences.conversationIdConsentState(group.id) + isAllowed = await bo.preferences.conversationConsentState(group.id) assert( isAllowed === 'allowed', `bo group should be allowed2 but was ${isAllowed}` @@ -1877,7 +1232,7 @@ test('sync function behaves as expected', async () => { let boGroups = await bo.conversations.listGroups() assert(boGroups.length === 0, 'num groups for bo is 0 until we sync') - await bo.conversations.syncConversations() + await bo.conversations.sync() boGroups = await bo.conversations.listGroups() assert(boGroups.length === 1, 'num groups for bo is 1') @@ -1890,7 +1245,7 @@ test('sync function behaves as expected', async () => { let numMessages = (await boGroups[0].messages()).length assert(numMessages === 0, 'num members should be 1') - await bo.conversations.syncConversations() + await bo.conversations.sync() // Num messages is still 0 because we didnt sync the group itself numMessages = (await boGroups[0].messages()).length @@ -1907,7 +1262,7 @@ test('sync function behaves as expected', async () => { numMembers = (await boGroups[0].memberInboxIds()).length assert(numMembers === 2, 'num members should be 2') - await bo.conversations.syncConversations() + await bo.conversations.sync() // Even though we synced the groups, we need to sync the group itself to see the new member numMembers = (await boGroups[0].memberInboxIds()).length @@ -1923,11 +1278,11 @@ test('sync function behaves as expected', async () => { bo.address, caro.address, ]) - await bo.conversations.syncConversations() + await bo.conversations.sync() boGroups = await bo.conversations.listGroups() assert(boGroups.length === 2, 'num groups for bo is 2') - // Even before syncing the group, syncConversations will return the initial number of members + // Even before syncing the group, sync will return the initial number of members numMembers = (await boGroups[1].memberInboxIds()).length assert(numMembers === 3, 'num members should be 3') @@ -1953,7 +1308,7 @@ test('can read and update group name', async () => { 'group name should be "Test name update 1"' ) - await bo.conversations.syncConversations() + await bo.conversations.sync() const boGroup = (await bo.conversations.listGroups())[0] groupName = await boGroup.groupName() @@ -1969,7 +1324,7 @@ test('can read and update group name', async () => { ) await alixGroup.addMembers([caro.address]) - await caro.conversations.syncConversations() + await caro.conversations.sync() const caroGroup = (await caro.conversations.listGroups())[0] await caroGroup.sync() @@ -2002,7 +1357,7 @@ test('can list groups does not fork', async () => { console.log('sent group message') // #endregion // #region sync groups - await bo.conversations.syncConversations() + await bo.conversations.sync() // #endregion const boGroups = await bo.conversations.listGroups() assert(boGroups.length === 1, 'bo should have 1 group') @@ -2057,70 +1412,6 @@ test('can list groups does not fork', async () => { return true }) -test('can create new installation without breaking group', async () => { - const keyBytes = new Uint8Array([ - 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, - 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, - ]) - const wallet1 = Wallet.createRandom() - const wallet2 = Wallet.createRandom() - - const client1 = await Client.create(wallet1, { - env: 'local', - appVersion: 'Testing/0.0.0', - enableV3: true, - dbEncryptionKey: keyBytes, - }) - const client2 = await Client.create(wallet2, { - env: 'local', - appVersion: 'Testing/0.0.0', - enableV3: true, - dbEncryptionKey: keyBytes, - }) - - const group = await client1.conversations.newGroup([wallet2.address]) - - await client1.conversations.syncConversations() - await client2.conversations.syncConversations() - - const client1Group = await client1.conversations.findGroup(group.id) - const client2Group = await client2.conversations.findGroup(group.id) - - await client1Group?.sync() - await client2Group?.sync() - - const members1 = await client1Group?.members() - assert( - members1?.length === 2, - `client 1 should see 2 members but was ${members1?.length}` - ) - - const members2 = await client2Group?.members() - assert( - members2?.length === 2, - `client 2 should see 2 members but was ${members2?.length}` - ) - - await client2.deleteLocalDatabase() - - // Recreating a client with wallet 2 (new installation!) - await Client.create(wallet2, { - env: 'local', - appVersion: 'Testing/0.0.0', - enableV3: true, - dbEncryptionKey: keyBytes, - }) - - await client1Group?.send('This message will break the group') - const members3 = await client1Group?.members() - assert( - members3?.length === 2, - `client 1 should still see the 2 members but was ${members3?.length}` - ) - - return true -}) - test('can list many groups members in parallel', async () => { const [alix, bo] = await createClients(2) const groups: Group[] = await createGroups(alix, [bo], 20) @@ -2145,7 +1436,7 @@ test('can sync all groups', async () => { const groups: Group[] = await createGroups(alix, [bo], 50) const alixGroup = groups[0] - await bo.conversations.syncConversations() + await bo.conversations.sync() const boGroup = await bo.conversations.findGroup(alixGroup.id) await alixGroup.send('hi') assert( @@ -2184,7 +1475,6 @@ test('can sync all groups', async () => { }) test('only streams groups that can be decrypted', async () => { - // Create three MLS enabled Clients const [alixClient, boClient, caroClient] = await createClients(3) const alixGroups: Conversation[] = [] const boGroups: Conversation[] = [] @@ -2192,13 +1482,13 @@ test('only streams groups that can be decrypted', async () => { await alixClient.conversations.stream(async (group: Conversation) => { alixGroups.push(group) - }) + }, 'groups') await boClient.conversations.stream(async (group: Conversation) => { boGroups.push(group) - }) + }, 'groups') await caroClient.conversations.stream(async (group: Conversation) => { caroGroups.push(group) - }) + }, 'groups') await alixClient.conversations.newGroup([boClient.address]) @@ -2246,6 +1536,70 @@ test('can stream groups and messages', async () => { return true }) +test('can create new installation without breaking group', async () => { + const keyBytes = new Uint8Array([ + 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, + 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, + ]) + const wallet1 = Wallet.createRandom() + const wallet2 = Wallet.createRandom() + + const client1 = await Client.create(wallet1, { + env: 'local', + appVersion: 'Testing/0.0.0', + enableV3: true, + dbEncryptionKey: keyBytes, + }) + const client2 = await Client.create(wallet2, { + env: 'local', + appVersion: 'Testing/0.0.0', + enableV3: true, + dbEncryptionKey: keyBytes, + }) + + const group = await client1.conversations.newGroup([wallet2.address]) + + await client1.conversations.sync() + await client2.conversations.sync() + + const client1Group = await client1.conversations.findGroup(group.id) + const client2Group = await client2.conversations.findGroup(group.id) + + await client1Group?.sync() + await client2Group?.sync() + + const members1 = await client1Group?.members() + assert( + members1?.length === 2, + `client 1 should see 2 members but was ${members1?.length}` + ) + + const members2 = await client2Group?.members() + assert( + members2?.length === 2, + `client 2 should see 2 members but was ${members2?.length}` + ) + + await client2.deleteLocalDatabase() + + // Recreating a client with wallet 2 (new installation!) + await Client.create(wallet2, { + env: 'local', + appVersion: 'Testing/0.0.0', + enableV3: true, + dbEncryptionKey: keyBytes, + }) + + await client1Group?.send('This message will break the group') + const members3 = await client1Group?.members() + assert( + members3?.length === 2, + `client 1 should still see the 2 members but was ${members3?.length}` + ) + + return true +}) + // Commenting this out so it doesn't block people, but nice to have? // test('can stream messages for a long time', async () => { // const bo = await Client.createRandom({ env: 'local', enableV3: true }) diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index a4968e1fd..c83ee6ae7 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -545,7 +545,7 @@ public class XMTPModule: Module { } } - AsyncFunction("findDm") { + AsyncFunction("findDmByAddress") { (inboxId: String, peerAddress: String) -> String? in guard let client = await clientsManager.getClient(key: inboxId) else { diff --git a/src/index.ts b/src/index.ts index 61053c613..df5f074b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -73,6 +73,10 @@ export async function requestMessageHistorySync(inboxId: InboxId) { return XMTPModule.requestMessageHistorySync(inboxId) } +export async function revokeAllOtherInstallations(inboxId: InboxId) { + return XMTPModule.revokeAllOtherInstallations(inboxId) +} + export async function getInboxState( inboxId: InboxId, refreshFromNetwork: boolean @@ -81,8 +85,8 @@ export async function getInboxState( return InboxState.from(inboxState) } -export async function revokeAllOtherInstallations(inboxId: InboxId) { - return XMTPModule.revokeAllOtherInstallations(inboxId) +export function preAuthenticateToInboxCallbackCompleted() { + XMTPModule.preAuthenticateToInboxCallbackCompleted() } export async function receiveSignature(requestID: string, signature: string) { @@ -171,74 +175,42 @@ export async function dropClient(inboxId: InboxId) { return await XMTPModule.dropClient(inboxId) } -export async function findOrCreateDm< - ContentTypes extends DefaultContentTypes = DefaultContentTypes, ->( - client: Client, - peerAddress: Address -): Promise> { - const dm = JSON.parse( - await XMTPModule.findOrCreateDm(client.inboxId, peerAddress) - ) - return new Dm(client, dm) +export async function canMessage( + inboxId: InboxId, + peerAddresses: Address[] +): Promise<{ [key: Address]: boolean }> { + return await XMTPModule.canMessage(inboxId, peerAddresses) } -export async function createGroup< - ContentTypes extends DefaultContentTypes = DefaultContentTypes, ->( - client: Client, - peerAddresses: Address[], - permissionLevel: 'all_members' | 'admin_only' = 'all_members', - name: string = '', - imageUrlSquare: string = '', - description: string = '', - pinnedFrameUrl: string = '' -): Promise> { - const options: CreateGroupParams = { - name, - imageUrlSquare, - description, - pinnedFrameUrl, - } - const group = JSON.parse( - await XMTPModule.createGroup( - client.inboxId, - peerAddresses, - permissionLevel, - JSON.stringify(options) - ) - ) - - return new Group(client, group) +export async function getOrCreateInboxId( + address: Address, + environment: XMTPEnvironment +): Promise { + return await XMTPModule.getOrCreateInboxId(getAddress(address), environment) } -export async function createGroupCustomPermissions< - ContentTypes extends DefaultContentTypes = DefaultContentTypes, ->( - client: Client, - peerAddresses: Address[], - permissionPolicySet: PermissionPolicySet, - name: string = '', - imageUrlSquare: string = '', - description: string = '', - pinnedFrameUrl: string = '' -): Promise> { - const options: CreateGroupParams = { - name, - imageUrlSquare, - description, - pinnedFrameUrl, - } - const group = JSON.parse( - await XMTPModule.createGroupCustomPermissions( - client.inboxId, - peerAddresses, - JSON.stringify(permissionPolicySet), - JSON.stringify(options) - ) +export async function encryptAttachment( + inboxId: InboxId, + file: DecryptedLocalAttachment +): Promise { + const fileJson = JSON.stringify(file) + const encryptedFileJson = await XMTPModule.encryptAttachment( + inboxId, + fileJson ) + return JSON.parse(encryptedFileJson) +} - return new Group(client, group) +export async function decryptAttachment( + inboxId: InboxId, + encryptedFile: EncryptedLocalAttachment +): Promise { + const encryptedFileJson = JSON.stringify(encryptedFile) + const fileJson = await XMTPModule.decryptAttachment( + inboxId, + encryptedFileJson + ) + return JSON.parse(fileJson) } export async function listGroups< @@ -316,54 +288,6 @@ export async function listConversations< }) } -export async function listMemberInboxIds< - ContentTypes extends DefaultContentTypes = DefaultContentTypes, ->(client: Client, id: ConversationId): Promise { - return XMTPModule.listMemberInboxIds(client.inboxId, id) -} - -export async function listPeerInboxId< - ContentTypes extends DefaultContentTypes = DefaultContentTypes, ->(client: Client, dmId: ConversationId): Promise { - return XMTPModule.listPeerInboxId(client.inboxId, dmId) -} - -export async function listConversationMembers( - inboxId: InboxId, - id: ConversationId -): Promise { - const members = await XMTPModule.listConversationMembers(inboxId, id) - - return members.map((json: string) => { - return Member.from(json) - }) -} - -export async function prepareMessage( - inboxId: InboxId, - conversationId: ConversationId, - content: any -): Promise { - const contentJson = JSON.stringify(content) - return await XMTPModule.prepareMessage(inboxId, conversationId, contentJson) -} - -export async function sendMessage( - inboxId: InboxId, - conversationId: ConversationId, - content: any -): Promise { - const contentJson = JSON.stringify(content) - return await XMTPModule.sendMessage(inboxId, conversationId, contentJson) -} - -export async function publishPreparedMessages( - inboxId: InboxId, - conversationId: ConversationId -) { - return await XMTPModule.publishPreparedMessages(inboxId, conversationId) -} - export async function conversationMessages< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( @@ -387,6 +311,16 @@ export async function conversationMessages< }) } +export async function findMessage< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + messageId: MessageId +): Promise | undefined> { + const message = await XMTPModule.findMessage(client.inboxId, messageId) + return DecodedMessage.from(message, client) +} + export async function findGroup< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( @@ -440,13 +374,13 @@ export async function findConversationByTopic< } } -export async function findDm< +export async function findDmByAddress< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( client: Client, address: Address ): Promise | undefined> { - const json = await XMTPModule.findDm(client.inboxId, address) + const json = await XMTPModule.findDmByAddress(client.inboxId, address) const dm = JSON.parse(json) if (!dm || Object.keys(dm).length === 0) { return undefined @@ -455,14 +389,122 @@ export async function findDm< return new Dm(client, dm) } -export async function findMessage< - ContentTypes extends DefaultContentTypes = DefaultContentTypes, ->( - client: Client, - messageId: MessageId -): Promise | undefined> { - const message = await XMTPModule.findMessage(client.inboxId, messageId) - return DecodedMessage.from(message, client) +export async function sendMessage( + inboxId: InboxId, + conversationId: ConversationId, + content: any +): Promise { + const contentJson = JSON.stringify(content) + return await XMTPModule.sendMessage(inboxId, conversationId, contentJson) +} + +export async function publishPreparedMessages( + inboxId: InboxId, + conversationId: ConversationId +) { + return await XMTPModule.publishPreparedMessages(inboxId, conversationId) +} + +export async function prepareMessage( + inboxId: InboxId, + conversationId: ConversationId, + content: any +): Promise { + const contentJson = JSON.stringify(content) + return await XMTPModule.prepareMessage(inboxId, conversationId, contentJson) +} + +export async function findOrCreateDm< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + peerAddress: Address +): Promise> { + const dm = JSON.parse( + await XMTPModule.findOrCreateDm(client.inboxId, peerAddress) + ) + return new Dm(client, dm) +} + +export async function createGroup< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + peerAddresses: Address[], + permissionLevel: 'all_members' | 'admin_only' = 'all_members', + name: string = '', + imageUrlSquare: string = '', + description: string = '', + pinnedFrameUrl: string = '' +): Promise> { + const options: CreateGroupParams = { + name, + imageUrlSquare, + description, + pinnedFrameUrl, + } + const group = JSON.parse( + await XMTPModule.createGroup( + client.inboxId, + peerAddresses, + permissionLevel, + JSON.stringify(options) + ) + ) + + return new Group(client, group) +} + +export async function createGroupCustomPermissions< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + peerAddresses: Address[], + permissionPolicySet: PermissionPolicySet, + name: string = '', + imageUrlSquare: string = '', + description: string = '', + pinnedFrameUrl: string = '' +): Promise> { + const options: CreateGroupParams = { + name, + imageUrlSquare, + description, + pinnedFrameUrl, + } + const group = JSON.parse( + await XMTPModule.createGroupCustomPermissions( + client.inboxId, + peerAddresses, + JSON.stringify(permissionPolicySet), + JSON.stringify(options) + ) + ) + + return new Group(client, group) +} + +export async function listMemberInboxIds< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>(client: Client, id: ConversationId): Promise { + return XMTPModule.listMemberInboxIds(client.inboxId, id) +} + +export async function dmPeerInboxId< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>(client: Client, dmId: ConversationId): Promise { + return XMTPModule.dmPeerInboxId(client.inboxId, dmId) +} + +export async function listConversationMembers( + inboxId: InboxId, + id: ConversationId +): Promise { + const members = await XMTPModule.listConversationMembers(inboxId, id) + + return members.map((json: string) => { + return Member.from(json) + }) } export async function syncConversations(inboxId: InboxId) { @@ -509,19 +551,19 @@ export async function removeGroupMembersByInboxId( return XMTPModule.removeGroupMembersByInboxId(inboxId, id, inboxIds) } -export function groupDescription( +export function groupName( inboxId: InboxId, id: ConversationId ): string | PromiseLike { - return XMTPModule.groupDescription(inboxId, id) + return XMTPModule.groupName(inboxId, id) } -export function updateGroupDescription( +export function updateGroupName( inboxId: InboxId, id: ConversationId, - description: string + groupName: string ): Promise { - return XMTPModule.updateGroupDescription(inboxId, id, description) + return XMTPModule.updateGroupName(inboxId, id, groupName) } export function groupImageUrlSquare( @@ -539,19 +581,19 @@ export function updateGroupImageUrlSquare( return XMTPModule.updateGroupImageUrlSquare(inboxId, id, imageUrlSquare) } -export function groupName( +export function groupDescription( inboxId: InboxId, id: ConversationId ): string | PromiseLike { - return XMTPModule.groupName(inboxId, id) + return XMTPModule.groupDescription(inboxId, id) } -export function updateGroupName( +export function updateGroupDescription( inboxId: InboxId, id: ConversationId, - groupName: string + description: string ): Promise { - return XMTPModule.updateGroupName(inboxId, id, groupName) + return XMTPModule.updateGroupDescription(inboxId, id, description) } export function groupPinnedFrameUrl( @@ -569,133 +611,7 @@ export function updateGroupPinnedFrameUrl( return XMTPModule.updateGroupPinnedFrameUrl(inboxId, id, pinnedFrameUrl) } -export async function canMessage( - inboxId: InboxId, - peerAddresses: Address[] -): Promise<{ [key: Address]: boolean }> { - return await XMTPModule.canMessage(inboxId, peerAddresses) -} - -export async function getOrCreateInboxId( - address: Address, - environment: XMTPEnvironment -): Promise { - return await XMTPModule.getOrCreateInboxId(getAddress(address), environment) -} - -export async function encryptAttachment( - file: DecryptedLocalAttachment -): Promise { - const fileJson = JSON.stringify(file) - const encryptedFileJson = await XMTPModule.encryptAttachment( - inboxId, - fileJson - ) - return JSON.parse(encryptedFileJson) -} - -export async function decryptAttachment( - encryptedFile: EncryptedLocalAttachment -): Promise { - const encryptedFileJson = JSON.stringify(encryptedFile) - const fileJson = await XMTPModule.decryptAttachment( - inboxId, - encryptedFileJson - ) - return JSON.parse(fileJson) -} - -export function subscribeToConversations( - inboxId: InboxId, - type: ConversationType -) { - return XMTPModule.subscribeToConversations(inboxId, type) -} - -export function subscribeToAllMessages( - inboxId: InboxId, - type: ConversationType -) { - return XMTPModule.subscribeToAllMessages(inboxId, type) -} - -export async function subscribeToMessages( - inboxId: InboxId, - id: ConversationId -) { - return await XMTPModule.subscribeToMessages(inboxId, id) -} - -export function unsubscribeFromConversations(inboxId: InboxId) { - return XMTPModule.unsubscribeFromConversations(inboxId) -} - -export function unsubscribeFromAllMessages(inboxId: InboxId) { - return XMTPModule.unsubscribeFromAllMessages(inboxId) -} - -export async function unsubscribeFromMessages( - inboxId: InboxId, - id: ConversationId -) { - return await XMTPModule.unsubscribeFromMessages(inboxId, id) -} - -export function registerPushToken(pushServer: string, token: string) { - return XMTPModule.registerPushToken(pushServer, token) -} - -export function subscribePushTopics(topics: ConversationTopic[]) { - return XMTPModule.subscribePushTopics(topics) -} - -export async function conversationConsentState( - inboxId: InboxId, - conversationId: ConversationId -): Promise { - return await XMTPModule.conversationV3ConsentState(inboxId, conversationId) -} - -export async function consentConversationIdState( - inboxId: InboxId, - conversationId: ConversationId -): Promise { - return await XMTPModule.consentConversationIdState(inboxId, conversationId) -} - -export async function consentInboxIdState( - inboxId: InboxId, - peerInboxId: InboxId -): Promise { - return await XMTPModule.consentInboxIdState(inboxId, peerInboxId) -} - -export async function consentAddressState( - inboxId: InboxId, - address: Address -): Promise { - return await XMTPModule.consentAddressState(inboxId, address) -} - -export async function setConsentState( - inboxId: InboxId, - value: string, - entryType: ConsentListEntryType, - consentType: ConsentState -): Promise { - return await XMTPModule.setConsentState( - inboxId, - value, - entryType, - consentType - ) -} - -export function preAuthenticateToInboxCallbackCompleted() { - XMTPModule.preAuthenticateToInboxCallbackCompleted() -} - -export async function isGroupActive( +export function isGroupActive( inboxId: InboxId, id: ConversationId ): Promise { @@ -882,14 +798,6 @@ export async function permissionPolicySet( return JSON.parse(json) } -export async function updateConversationConsent( - inboxId: InboxId, - conversationId: ConversationId, - state: ConsentState -): Promise { - return XMTPModule.updateConversationConsent(inboxId, conversationId, state) -} - export async function processMessage< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( @@ -907,7 +815,7 @@ export async function processWelcomeMessage< client: Client, encryptedMessage: string ): Promise>> { - const json = await XMTPModule.processConversationWelcomeMessage( + const json = await XMTPModule.processWelcomeMessage( client.inboxId, encryptedMessage ) @@ -920,6 +828,97 @@ export async function processWelcomeMessage< } } +export async function setConsentState( + inboxId: InboxId, + value: string, + entryType: ConsentListEntryType, + consentType: ConsentState +): Promise { + return await XMTPModule.setConsentState( + inboxId, + value, + entryType, + consentType + ) +} + +export async function consentAddressState( + inboxId: InboxId, + address: Address +): Promise { + return await XMTPModule.consentAddressState(inboxId, address) +} +export async function consentInboxIdState( + inboxId: InboxId, + peerInboxId: InboxId +): Promise { + return await XMTPModule.consentInboxIdState(inboxId, peerInboxId) +} +export async function consentConversationIdState( + inboxId: InboxId, + conversationId: ConversationId +): Promise { + return await XMTPModule.consentConversationIdState(inboxId, conversationId) +} +export async function conversationConsentState( + inboxId: InboxId, + conversationId: ConversationId +): Promise { + return await XMTPModule.conversationConsentState(inboxId, conversationId) +} + +export async function updateConversationConsent( + inboxId: InboxId, + conversationId: ConversationId, + state: ConsentState +): Promise { + return XMTPModule.updateConversationConsent(inboxId, conversationId, state) +} + +export function subscribeToConversations( + inboxId: InboxId, + type: ConversationType +) { + return XMTPModule.subscribeToConversations(inboxId, type) +} + +export function subscribeToAllMessages( + inboxId: InboxId, + type: ConversationType +) { + return XMTPModule.subscribeToAllMessages(inboxId, type) +} + +export async function subscribeToMessages( + inboxId: InboxId, + id: ConversationId +) { + return await XMTPModule.subscribeToMessages(inboxId, id) +} + +export function unsubscribeFromConversations(inboxId: InboxId) { + return XMTPModule.unsubscribeFromConversations(inboxId) +} + +export function unsubscribeFromAllMessages(inboxId: InboxId) { + return XMTPModule.unsubscribeFromAllMessages(inboxId) +} + +export async function unsubscribeFromMessages( + inboxId: InboxId, + id: ConversationId +) { + return await XMTPModule.unsubscribeFromMessages(inboxId, id) +} + +export function registerPushToken(pushServer: string, token: string) { + return XMTPModule.registerPushToken(pushServer, token) +} + +export function subscribePushTopics(topics: ConversationTopic[]) { + return XMTPModule.subscribePushTopics(topics) +} + export async function exportNativeLogs() { return XMTPModule.exportNativeLogs() } diff --git a/src/lib/Client.ts b/src/lib/Client.ts index d141cbf57..086d3d360 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -34,7 +34,7 @@ export class Client< installationId: string dbPath: string conversations: Conversations - zpreferences: PrivatePreferences + preferences: PrivatePreferences codecRegistry: { [key: string]: XMTPModule.ContentCodec } private static signSubscription: Subscription | null = null private static authSubscription: Subscription | null = null @@ -174,13 +174,13 @@ export class Client< Boolean(authInboxSubscription), options.dbDirectory, options.historySyncUrl, - signer.walletType(), - signer.getChainId(), - signer.getBlockNumber() + signer.walletType?.(), + signer.getChainId?.(), + signer.getBlockNumber?.() ) })().catch((error) => { this.removeAllSubscriptions(authInboxSubscription) - console.error('ERROR in create: ', error) + console.error('ERROR in create: ', error.message) }) }) } @@ -434,7 +434,7 @@ export class Client< if (!file.fileUri?.startsWith('file://')) { throw new Error('the attachment must be a local file:// uri') } - return await XMTPModule.encryptAttachment(file) + return await XMTPModule.encryptAttachment(this.inboxId, file) } /** @@ -451,7 +451,7 @@ export class Client< if (!encryptedFile.encryptedLocalFileUri?.startsWith('file://')) { throw new Error('the attachment must be a local file:// uri') } - return await XMTPModule.decryptAttachment(encryptedFile) + return await XMTPModule.decryptAttachment(this.inboxId, encryptedFile) } } export type XMTPEnvironment = 'local' | 'dev' | 'production' diff --git a/src/lib/Conversations.ts b/src/lib/Conversations.ts index e9f134fa2..bd0873056 100644 --- a/src/lib/Conversations.ts +++ b/src/lib/Conversations.ts @@ -77,13 +77,13 @@ export default class Conversations< } /** - * This method returns a list of all groups that the client is a member of. - * To get the latest list of groups from the network, call syncGroups() first. - * @param {ConversationOptions} opts - The options to specify what fields you want returned for the groups in the list. - * @param {ConversationOrder} order - The order to specify if you want groups listed by last message or by created at. - * @param {number} limit - Limit the number of groups returned in the list. + * This method returns a list of all dms that the client is a member of. + * To get the latest list of dms from the network, call sync() first. + * @param {ConversationOptions} opts - The options to specify what fields you want returned for the dms in the list. + * @param {ConversationOrder} order - The order to specify if you want dms listed by last message or by created at. + * @param {number} limit - Limit the number of dms returned in the list. * - * @returns {Promise} A Promise that resolves to an array of Group objects. + * @returns {Promise} A Promise that resolves to an array of Dms objects. */ async listDms( opts?: ConversationOptions | undefined, @@ -95,7 +95,7 @@ export default class Conversations< /** * This method returns a group by the group id if that group exists in the local database. - * To get the latest list of groups from the network, call syncGroups() first. + * To get the latest list of groups from the network, call sync() first. * * @returns {Promise} A Promise that resolves to a Group or undefined if not found. */ @@ -107,9 +107,9 @@ export default class Conversations< /** * This method returns a Dm by the address if that dm exists in the local database. - * To get the latest list of groups from the network, call syncConversations() first. + * To get the latest list of dms from the network, call sync() first. * - * @returns {Promise} A Promise that resolves to a Group or undefined if not found. + * @returns {Promise} A Promise that resolves to a Dm or undefined if not found. */ async findDm(address: Address): Promise | undefined> { return await XMTPModule.findDm(this.client, address) @@ -117,9 +117,9 @@ export default class Conversations< /** * This method returns a conversation by the topic if that conversation exists in the local database. - * To get the latest list of groups from the network, call syncConversations() first. + * To get the latest list of conversations from the network, call sync() first. * - * @returns {Promise} A Promise that resolves to a Group or undefined if not found. + * @returns {Promise} A Promise that resolves to a Conversation or undefined if not found. */ async findConversationByTopic( topic: ConversationTopic @@ -129,9 +129,9 @@ export default class Conversations< /** * This method returns a conversation by the conversation id if that conversation exists in the local database. - * To get the latest list of groups from the network, call syncConversations() first. + * To get the latest list of conversations from the network, call sync() first. * - * @returns {Promise} A Promise that resolves to a Group or undefined if not found. + * @returns {Promise} A Promise that resolves to a Conversation or undefined if not found. */ async findConversation( conversationId: ConversationId @@ -141,7 +141,7 @@ export default class Conversations< /** * This method returns a message by the message id if that message exists in the local database. - * To get the latest list of messages from the network, call syncGroups() first. + * To get the latest list of messages from the network, call sync() first. * * @returns {Promise} A Promise that resolves to a DecodedMessage or undefined if not found. */ @@ -153,7 +153,7 @@ export default class Conversations< /** * This method returns a list of all V3 conversations that the client is a member of. - * To include the latest groups from the network in the returned list, call syncGroups() first. + * To include the latest conversations from the network in the returned list, call sync() first. * * @returns {Promise} A Promise that resolves to an array of Conversation objects. */ diff --git a/src/lib/Dm.ts b/src/lib/Dm.ts index 601258496..9a54216d7 100644 --- a/src/lib/Dm.ts +++ b/src/lib/Dm.ts @@ -43,8 +43,7 @@ export class Dm } /** - * This method returns an array of inbox ids associated with the group. - * To get the latest member inbox ids from the network, call sync() first. + * This method return the peer inbox id associated with the dm. * @returns {Promise} A Promise that resolves to a InboxId. */ async peerInboxId(): Promise { @@ -52,7 +51,7 @@ export class Dm } /** - * Sends a message to the current group. + * Sends a message to the current dm. * * @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. @@ -79,7 +78,7 @@ export class Dm } /** - * Prepare a group message to be sent. + * Prepare a dm 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. @@ -120,7 +119,7 @@ export class Dm } /** - * This method returns an array of messages associated with the group. + * This method returns an array of messages associated with the dm. * To get the latest messages from the network, call sync() first. * * @param {number | undefined} limit - Optional maximum number of messages to return. @@ -144,14 +143,14 @@ export class Dm /** * Executes a network request to fetch the latest messages and membership changes - * associated with the group and saves them to the local state. + * associated with the dm and saves them to the local state. */ async sync() { await XMTP.syncConversation(this.client.inboxId, this.id) } /** - * Sets up a real-time message stream for the current group. + * Sets up a real-time message stream for the current dm. * * This method subscribes to incoming messages in real-time and listens for new message events. * When a new message is detected, the provided callback function is invoked with the details of the message. diff --git a/src/lib/Group.ts b/src/lib/Group.ts index 0c785b7b3..38861c938 100644 --- a/src/lib/Group.ts +++ b/src/lib/Group.ts @@ -200,17 +200,16 @@ export class Group< async ({ inboxId, message, - groupId, + conversationId, }: { inboxId: string message: DecodedMessage - groupId: string + conversationId: string }) => { - // Long term these checks should be able to be done on the native layer as well, but additional checks in JS for safety if (inboxId !== this.client.inboxId) { return } - if (groupId !== this.id) { + if (conversationId !== this.id) { return } @@ -223,7 +222,6 @@ export class Group< await XMTP.unsubscribeFromMessages(this.client.inboxId, this.id) } } - /** * * @param addresses addresses to add to the group diff --git a/src/lib/PrivatePreferences.ts b/src/lib/PrivatePreferences.ts index 7bd355c1e..04c8e7825 100644 --- a/src/lib/PrivatePreferences.ts +++ b/src/lib/PrivatePreferences.ts @@ -11,7 +11,7 @@ export default class PrivatePreferences { this.client = client } - async conversationIdConsentState( + async conversationConsentState( conversationId: ConversationId ): Promise { return await XMTPModule.consentConversationIdState( diff --git a/src/lib/Signer.ts b/src/lib/Signer.ts index efc994d27..97946beba 100644 --- a/src/lib/Signer.ts +++ b/src/lib/Signer.ts @@ -14,6 +14,8 @@ export function getSigner(wallet: Signer | WalletClient | null): Signer | null { if (!wallet) { return null } + console.log("Lopi444") + if (isWalletClient(wallet)) { return convertWalletClientToSigner(wallet) }