diff --git a/example/src/tests/test-utils.ts b/example/src/tests/test-utils.ts index 6d1023938..ce7c2209b 100644 --- a/example/src/tests/test-utils.ts +++ b/example/src/tests/test-utils.ts @@ -39,3 +39,29 @@ export async function createClients(numClients: number): Promise { } return clients } + +export async function createV3TestingClients(): Promise { + const clients = [] + 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 alix = await Client.createRandom({ + env: 'local', + }) + const bo = await Client.createRandomV3({ + env: 'local', + enableV3: true, + dbEncryptionKey: keyBytes, + }) + const caro = await Client.createRandom({ + env: 'local', + enableV3: true, + dbEncryptionKey: keyBytes, + }) + bo.register(new GroupUpdatedCodec()) + caro.register(new GroupUpdatedCodec()) + + clients.push(alix, bo, caro) + return clients +} diff --git a/example/src/tests/v3OnlyTests.ts b/example/src/tests/v3OnlyTests.ts index e298fdb22..17579a7b3 100644 --- a/example/src/tests/v3OnlyTests.ts +++ b/example/src/tests/v3OnlyTests.ts @@ -1,7 +1,12 @@ /* eslint-disable @typescript-eslint/no-extra-non-null-assertion */ -import { Client, Group } from 'xmtp-react-native-sdk' +import { Client } from 'xmtp-react-native-sdk' -import { Test, assert, createClients } from './test-utils' +import { + Test, + assert, + createV3TestingClients, + delayToPropogate, +} from './test-utils' export const v3OnlyTests: Test[] = [] let counter = 1 @@ -33,34 +38,98 @@ test('can make a V3 only client', async () => { client.inboxId === inboxId, `inboxIds should match but were ${client.inboxId} and ${inboxId}` ) - const canMessageV2 = await client.canMessage(client.address) - assert(canMessageV2 === false, `canMessageV2 should be false`) const canMessageV3 = await client.canGroupMessage([client.address]) assert( canMessageV3[client.address.toLowerCase()] === true, `canMessageV3 should be true` ) + try { + await client.canMessage(client.address) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + return true + } - return true + throw new Error('should throw error when hitting V2 api') }) test('can create group', async () => { + const [alixV2, boV3, caroV2V3] = await createV3TestingClients() + const group = await boV3.conversations.newGroup([caroV2V3.address]) + assert(group?.members?.length === 2, `group should have 2 members`) - return true + try { + await boV3.conversations.newGroup([alixV2.address]) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + return true + } + throw new Error( + 'should throw error when trying to add a V2 only client to a group' + ) }) test('can send message', async () => { + const [alixV2, boV3, caroV2V3] = await createV3TestingClients() + const group = await boV3.conversations.newGroup([caroV2V3.address]) + await group.send('gm') + await group.sync() + const groupMessages = await group.messages() + assert( + groupMessages[0].content() === 'gm', + `first should be gm but was ${groupMessages[0].content()}` + ) + await caroV2V3.conversations.syncGroups() + const sameGroups = await caroV2V3.conversations.listGroups() + await sameGroups[0].sync() + + const sameGroupMessages = await sameGroups[0].messages() + assert( + sameGroupMessages[0].content() === 'gm', + `second should be gm but was ${sameGroupMessages[0].content()}` + ) return true }) test('can stream all messages', async () => { + const [alixV2, boV3, caroV2V3] = await createV3TestingClients() + const conversation = await alixV2.conversations.newConversation( + caroV2V3.address + ) + const group = await boV3.conversations.newGroup([caroV2V3.address]) + await caroV2V3.conversations.syncGroups() + + const allMessages: any[] = [] + + await caroV2V3.conversations.streamAllMessages(async (conversation) => { + allMessages.push(conversation) + }, true) + + await conversation.send('hi') + await group.send('hi') + + assert(allMessages.length === 2, '2 messages should have been streamed') return true }) test('can stream groups and conversations', async () => { + const [alixV2, boV3, caroV2V3] = await createV3TestingClients() + + const allConvos: any[] = [] + + await caroV2V3.conversations.streamAll(async (conversation) => { + allConvos.push(conversation) + }) + + await alixV2.conversations.newConversation(caroV2V3.address) + await boV3.conversations.newGroup([caroV2V3.address]) + + await delayToPropogate() + + assert(allConvos.length === 2, '2 convos should have been streamed') return true }) diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index a010de6e0..d1c365eb4 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -96,6 +96,7 @@ public class XMTPModule: Module { // Auth "sign", "authed", + "authedV3", "preCreateIdentityCallback", "preEnableIdentityCallback", "preAuthenticateToInboxCallback", @@ -280,6 +281,76 @@ public class XMTPModule: Module { throw error } } + + AsyncFunction("createRandomV3") { (hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, hasAuthenticateToInboxCallback: Bool?, dbEncryptionKey: [UInt8]?, authParams: String) -> [String: String] in + + let privateKey = try PrivateKey.generate() + if(hasCreateIdentityCallback ?? false) { + preCreateIdentityCallbackDeferred = DispatchSemaphore(value: 0) + } + if(hasEnableIdentityCallback ?? false) { + preEnableIdentityCallbackDeferred = DispatchSemaphore(value: 0) + } + if(hasAuthenticateToInboxCallback ?? false) { + preAuthenticateToInboxCallbackDeferred = DispatchSemaphore(value: 0) + } + let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil + let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil + let preAuthenticateToInboxCallback: PreEventCallback? = hasAuthenticateToInboxCallback ?? false ? self.preAuthenticateToInboxCallback : nil + let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) + let authOptions = AuthParamsWrapper.authParamsFromJson(authParams) + + let options = createClientConfig( + env: authOptions.environment, + appVersion: authOptions.appVersion, + preEnableIdentityCallback: preEnableIdentityCallback, + preCreateIdentityCallback: preCreateIdentityCallback, + preAuthenticateToInboxCallback: preAuthenticateToInboxCallback, + enableV3: authOptions.enableV3, + dbEncryptionKey: encryptionKeyData, + dbDirectory: authOptions.dbDirectory, + historySyncUrl: authOptions.historySyncUrl + ) + let client = try await Client.createOrBuild(account: privateKey, options: options) + + await clientsManager.updateClient(key: client.inboxID, client: client) + return try ClientWrapper.encodeToObj(client) + } + + AsyncFunction("createOrBuild") { (address: String, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, hasAuthenticateToInboxCallback: Bool?, dbEncryptionKey: [UInt8]?, authParams: String) in + let signer = ReactNativeSigner(module: self, address: address) + self.signer = signer + if(hasCreateIdentityCallback ?? false) { + self.preCreateIdentityCallbackDeferred = DispatchSemaphore(value: 0) + } + if(hasEnableIdentityCallback ?? false) { + self.preEnableIdentityCallbackDeferred = DispatchSemaphore(value: 0) + } + if(hasAuthenticateToInboxCallback ?? false) { + self.preAuthenticateToInboxCallbackDeferred = DispatchSemaphore(value: 0) + } + let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil + let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil + let preAuthenticateToInboxCallback: PreEventCallback? = hasAuthenticateToInboxCallback ?? false ? self.preAuthenticateToInboxCallback : nil + let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) + let authOptions = AuthParamsWrapper.authParamsFromJson(authParams) + + let options = self.createClientConfig( + env: authOptions.environment, + appVersion: authOptions.appVersion, + preEnableIdentityCallback: preEnableIdentityCallback, + preCreateIdentityCallback: preCreateIdentityCallback, + preAuthenticateToInboxCallback: preAuthenticateToInboxCallback, + enableV3: authOptions.enableV3, + dbEncryptionKey: encryptionKeyData, + dbDirectory: authOptions.dbDirectory, + historySyncUrl: authOptions.historySyncUrl + ) + let client = try await XMTP.Client.createOrBuild(account: signer, options: options) + await self.clientsManager.updateClient(key: client.inboxID, client: client) + self.signer = nil + self.sendEvent("authedV3", try ClientWrapper.encodeToObj(client)) + } // Remove a client from memory for a given inboxId AsyncFunction("dropClient") { (inboxId: String) in diff --git a/src/lib/types/EventTypes.ts b/src/lib/types/EventTypes.ts index 5c5589500..26a41ab5d 100644 --- a/src/lib/types/EventTypes.ts +++ b/src/lib/types/EventTypes.ts @@ -2,6 +2,7 @@ export enum EventTypes { // Auth Sign = 'sign', Authed = 'authed', + AuthedV3 = 'authedV3', PreCreateIdentityCallback = 'preCreateIdentityCallback', PreEnableIdentityCallback = 'preEnableIdentityCallback', // Conversations Events