From bbb4e60d4d1841ff44912ff91b2a45ffd922f4e6 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 31 Oct 2024 20:55:26 -0700 Subject: [PATCH 1/7] add ability to create from key bundle with signer --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 28 +++++ src/index.ts | 29 +++++ src/lib/Client.ts | 107 +++++++++++++++--- 3 files changed, 146 insertions(+), 18 deletions(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 8aa94dbea..6b13181ec 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -410,6 +410,34 @@ class XMTPModule : Module() { } } + AsyncFunction("createFromKeyBundleWithSigner") Coroutine { address: String, keyBundle: String, dbEncryptionKey: List?, authParams: String -> + withContext(Dispatchers.IO) { + logV("createFromKeyBundleWithSigner") + try { + val options = clientOptions( + dbEncryptionKey, + authParams + ) + val bundle = + PrivateKeyOuterClass.PrivateKeyBundle.parseFrom( + Base64.decode( + keyBundle, + NO_WRAP + ) + ) + val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address) + signer = reactSigner + val client = Client().buildFromBundle(bundle = bundle, options = options, account = reactSigner) + clients[client.inboxId] = client + ContentJson.Companion + signer = null + sendEvent("authed", ClientWrapper.encodeToObj(client)) + } catch (e: Exception) { + throw XMTPException("Failed to create client: $e") + } + } + } + AsyncFunction("createV3") Coroutine { address: String, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, hasAuthInboxCallback: Boolean?, dbEncryptionKey: List?, authParams: String -> withContext(Dispatchers.IO) { logV("createV3") diff --git a/src/index.ts b/src/index.ts index 051713834..f555f1977 100644 --- a/src/index.ts +++ b/src/index.ts @@ -188,6 +188,35 @@ export async function createFromKeyBundle( ) } +export async function createFromKeyBundleWithSigner( + address: string, + keyBundle: string, + environment: 'local' | 'dev' | 'production', + appVersion?: string | undefined, + enableV3?: boolean | undefined, + dbEncryptionKey?: Uint8Array | undefined, + dbDirectory?: string | undefined, + historySyncUrl?: string | undefined +): Promise { + const encryptionKey = dbEncryptionKey + ? Array.from(dbEncryptionKey) + : undefined + + const authParams: AuthParams = { + environment, + appVersion, + enableV3, + dbDirectory, + historySyncUrl, + } + return await XMTPModule.createFromKeyBundleWithSigner( + address, + keyBundle, + encryptionKey, + JSON.stringify(authParams) + ) +} + export async function createRandomV3( environment: 'local' | 'dev' | 'production', appVersion?: string | undefined, diff --git a/src/lib/Client.ts b/src/lib/Client.ts index 4df46aac7..37ac433da 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -225,7 +225,8 @@ export class Client< ContentCodecs extends DefaultContentTypes = [], >( keyBundle: string, - options: ClientOptions & { codecs?: ContentCodecs } + options: ClientOptions & { codecs?: ContentCodecs }, + wallet?: Signer | WalletClient | undefined ): Promise> { if ( options.enableV3 === true && @@ -234,23 +235,93 @@ export class Client< ) { throw new Error('Must pass an encryption key that is exactly 32 bytes.') } - const client = await XMTPModule.createFromKeyBundle( - keyBundle, - options.env, - options.appVersion, - Boolean(options.enableV3), - options.dbEncryptionKey, - options.dbDirectory, - options.historySyncUrl - ) - return new Client( - client['address'], - client['inboxId'], - client['installationId'], - client['dbPath'], - options.codecs || [] - ) + if (!wallet) { + const client = await XMTPModule.createFromKeyBundle( + keyBundle, + options.env, + options.appVersion, + Boolean(options.enableV3), + options.dbEncryptionKey, + options.dbDirectory, + options.historySyncUrl + ) + + return new Client( + client['address'], + client['inboxId'], + client['installationId'], + client['dbPath'], + options.codecs || [] + ) + } else { + const signer = getSigner(wallet) + if (!signer) { + throw new Error('Signer is not configured') + } + return new Promise>((resolve, reject) => { + ;(async () => { + this.signSubscription = XMTPModule.emitter.addListener( + 'sign', + async (message: { id: string; message: string }) => { + const request: { id: string; message: string } = message + try { + const signatureString = await signer.signMessage( + request.message + ) + const eSig = splitSignature(signatureString) + const r = hexToBytes(eSig.r) + const s = hexToBytes(eSig.s) + const sigBytes = new Uint8Array(65) + sigBytes.set(r) + sigBytes.set(s, r.length) + sigBytes[64] = eSig.recoveryParam + + const signature = Buffer.from(sigBytes).toString('base64') + + await XMTPModule.receiveSignature(request.id, signature) + } catch (e) { + const errorMessage = 'ERROR in create. User rejected signature' + console.info(errorMessage, e) + reject(errorMessage) + } + } + ) + + this.authSubscription = XMTPModule.emitter.addListener( + 'authed', + async (message: { + inboxId: string + address: string + installationId: string + dbPath: string + }) => { + resolve( + new Client( + message.address, + message.inboxId as InboxId, + message.installationId, + message.dbPath, + options.codecs || [] + ) + ) + } + ) + await XMTPModule.createFromKeyBundleWithSigner( + await signer.getAddress(), + keyBundle, + options.env, + options.appVersion, + Boolean(options.enableV3), + options.dbEncryptionKey, + options.dbDirectory, + options.historySyncUrl + ) + })().catch((error) => { + console.error('ERROR in create: ', error) + }) + }) + } } /** @@ -412,7 +483,7 @@ export class Client< }) } - /** + /** * Builds a V3 ONLY instance of the Client class using the provided address and chainId if SCW. * * @param {string} address - The address of the account to build From 0624136bcf2897ff5486656cfd0e4f6cbbf20c99 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 31 Oct 2024 21:02:32 -0700 Subject: [PATCH 2/7] add the iOS method --- ios/XMTPModule.swift | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 1248fabc9..d3c954cb0 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -297,6 +297,31 @@ public class XMTPModule: Module { } } + AsyncFunction("createFromKeyBundleWithSigner") { (address: String, keyBundle: String, dbEncryptionKey: [UInt8]?, authParams: String) in + // V2 ONLY + do { + guard let keyBundleData = Data(base64Encoded: keyBundle), + let bundle = try? PrivateKeyBundle(serializedData: keyBundleData) + else { + throw Error.invalidKeyBundle + } + let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) + let authOptions = AuthParamsWrapper.authParamsFromJson(authParams) + + let signer = ReactNativeSigner(module: self, address: address) + self.signer = signer + + let options = createClientConfig(env: authOptions.environment, appVersion: authOptions.appVersion, enableV3: authOptions.enableV3, dbEncryptionKey: encryptionKeyData, dbDirectory: authOptions.dbDirectory, historySyncUrl: authOptions.historySyncUrl) + let client = try await Client.from(v1Bundle: bundle.v1, options: options, signingKey: signer) + await clientsManager.updateClient(key: client.inboxID, client: client) + self.signer = nil + self.sendEvent("authed", try ClientWrapper.encodeToObj(client)) + } catch { + print("ERROR! Failed to create client: \(error)") + throw error + } + } + AsyncFunction("createRandomV3") { (hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, hasAuthenticateToInboxCallback: Bool?, dbEncryptionKey: [UInt8]?, authParams: String) -> [String: String] in let privateKey = try PrivateKey.generate() From bfb492e3540e1ded95adac9289411cbd4a79e614 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 31 Oct 2024 21:07:08 -0700 Subject: [PATCH 3/7] add tests --- example/src/tests/groupTests.ts | 14 ++++++++++++++ example/src/tests/tests.ts | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index 55d4f0eb5..1693eecb4 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -63,6 +63,20 @@ test('can revoke all other installations', async () => { enableV3: true, dbEncryptionKey: keyBytes, }) + + const keyBundle = await alix.exportKeyBundle() + + await Client.createFromKeyBundle( + keyBundle, + { + env: 'local', + appVersion: 'Testing/0.0.0', + enableV3: true, + dbEncryptionKey: keyBytes, + }, + alixWallet + ) + await alix.deleteLocalDatabase() const alix2 = await Client.create(alixWallet, { diff --git a/example/src/tests/tests.ts b/example/src/tests/tests.ts index b34301a95..56b588c50 100644 --- a/example/src/tests/tests.ts +++ b/example/src/tests/tests.ts @@ -222,6 +222,16 @@ test('can load a client from env "2k lens convos" private key', async () => { env: 'local', }) + const keyBundle = await xmtpClient.exportKeyBundle() + + await Client.createFromKeyBundle( + keyBundle, + { + env: 'local', + }, + signer + ) + return true }) From 793c523ea5dbf0d63c9ceccc832a995633175fc9 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 1 Nov 2024 12:16:18 -0700 Subject: [PATCH 4/7] add more tests --- .../project.pbxproj | 70 +++++++++---------- example/src/tests/groupTests.ts | 45 ++++++++++++ 2 files changed, 80 insertions(+), 35 deletions(-) diff --git a/example/ios/xmtpreactnativesdkexample.xcodeproj/project.pbxproj b/example/ios/xmtpreactnativesdkexample.xcodeproj/project.pbxproj index 3388e7aa2..0d2d68566 100644 --- a/example/ios/xmtpreactnativesdkexample.xcodeproj/project.pbxproj +++ b/example/ios/xmtpreactnativesdkexample.xcodeproj/project.pbxproj @@ -11,7 +11,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; - 9453E5BA4D9E60C6B21EAF55 /* libPods-xmtpreactnativesdkexample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A15D051C53AD0CEEBB4113F5 /* libPods-xmtpreactnativesdkexample.a */; }; + 54E6B19EA8330AD1B859A861 /* libPods-xmtpreactnativesdkexample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DAE522F34BCEFD44E396D2 /* libPods-xmtpreactnativesdkexample.a */; }; A6A5DB882A00551E001DF8C2 /* xmtpreactnativesdkexampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6A5DB872A00551E001DF8C2 /* xmtpreactnativesdkexampleUITests.swift */; }; B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; }; BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; @@ -36,15 +36,15 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = xmtpreactnativesdkexample/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = xmtpreactnativesdkexample/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = xmtpreactnativesdkexample/main.m; sourceTree = ""; }; - 74E1B7F7695132E36345D810 /* Pods-xmtpreactnativesdkexample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xmtpreactnativesdkexample.release.xcconfig"; path = "Target Support Files/Pods-xmtpreactnativesdkexample/Pods-xmtpreactnativesdkexample.release.xcconfig"; sourceTree = ""; }; - A15D051C53AD0CEEBB4113F5 /* libPods-xmtpreactnativesdkexample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-xmtpreactnativesdkexample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 14DAE522F34BCEFD44E396D2 /* libPods-xmtpreactnativesdkexample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-xmtpreactnativesdkexample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 62E6C2FC52DB7774BD32A867 /* Pods-xmtpreactnativesdkexample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xmtpreactnativesdkexample.release.xcconfig"; path = "Target Support Files/Pods-xmtpreactnativesdkexample/Pods-xmtpreactnativesdkexample.release.xcconfig"; sourceTree = ""; }; + 6941C3A2B2A07289AC69DB6E /* Pods-xmtpreactnativesdkexample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xmtpreactnativesdkexample.debug.xcconfig"; path = "Target Support Files/Pods-xmtpreactnativesdkexample/Pods-xmtpreactnativesdkexample.debug.xcconfig"; sourceTree = ""; }; A6A5DB852A00551E001DF8C2 /* xmtpreactnativesdkexampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = xmtpreactnativesdkexampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A6A5DB872A00551E001DF8C2 /* xmtpreactnativesdkexampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = xmtpreactnativesdkexampleUITests.swift; sourceTree = ""; }; A6AE8C832A49F1F300BD4E8B /* libMessagePack.swift.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libMessagePack.swift.a; sourceTree = BUILT_PRODUCTS_DIR; }; AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = xmtpreactnativesdkexample/SplashScreen.storyboard; sourceTree = ""; }; BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - ED9CBB83C05BBF316FC66508 /* Pods-xmtpreactnativesdkexample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xmtpreactnativesdkexample.debug.xcconfig"; path = "Target Support Files/Pods-xmtpreactnativesdkexample/Pods-xmtpreactnativesdkexample.debug.xcconfig"; sourceTree = ""; }; FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-xmtpreactnativesdkexample/ExpoModulesProvider.swift"; sourceTree = ""; }; FDF0078FD601458DA88B0565 /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "xmtpreactnativesdkexample/noop-file.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -54,7 +54,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9453E5BA4D9E60C6B21EAF55 /* libPods-xmtpreactnativesdkexample.a in Frameworks */, + 54E6B19EA8330AD1B859A861 /* libPods-xmtpreactnativesdkexample.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -89,7 +89,7 @@ children = ( A6AE8C832A49F1F300BD4E8B /* libMessagePack.swift.a */, ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - A15D051C53AD0CEEBB4113F5 /* libPods-xmtpreactnativesdkexample.a */, + 14DAE522F34BCEFD44E396D2 /* libPods-xmtpreactnativesdkexample.a */, ); name = Frameworks; sourceTree = ""; @@ -154,8 +154,8 @@ D65327D7A22EEC0BE12398D9 /* Pods */ = { isa = PBXGroup; children = ( - ED9CBB83C05BBF316FC66508 /* Pods-xmtpreactnativesdkexample.debug.xcconfig */, - 74E1B7F7695132E36345D810 /* Pods-xmtpreactnativesdkexample.release.xcconfig */, + 6941C3A2B2A07289AC69DB6E /* Pods-xmtpreactnativesdkexample.debug.xcconfig */, + 62E6C2FC52DB7774BD32A867 /* Pods-xmtpreactnativesdkexample.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -175,14 +175,14 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "xmtpreactnativesdkexample" */; buildPhases = ( - 1A3E35BABB8B52C80119AEF4 /* [CP] Check Pods Manifest.lock */, + F7C2F8448A1C8A1337F9DFBF /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 0A30FD7A4347963420162925 /* [CP] Embed Pods Frameworks */, - E72368AA4D83F2FC000573E5 /* [CP] Copy Pods Resources */, + 4BAC9E432DE97602D88F93F8 /* [CP] Embed Pods Frameworks */, + 0241AAAE331A2389D024FC03 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -286,7 +286,27 @@ shellPath = /bin/sh; shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" $PROJECT_ROOT ios relative | tail -n 1)\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n"; }; - 0A30FD7A4347963420162925 /* [CP] Embed Pods Frameworks */ = { + 0241AAAE331A2389D024FC03 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-xmtpreactnativesdkexample/Pods-xmtpreactnativesdkexample-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-xmtpreactnativesdkexample/Pods-xmtpreactnativesdkexample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 4BAC9E432DE97602D88F93F8 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -306,7 +326,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-xmtpreactnativesdkexample/Pods-xmtpreactnativesdkexample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 1A3E35BABB8B52C80119AEF4 /* [CP] Check Pods Manifest.lock */ = { + F7C2F8448A1C8A1337F9DFBF /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -328,26 +348,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - E72368AA4D83F2FC000573E5 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-xmtpreactnativesdkexample/Pods-xmtpreactnativesdkexample-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-xmtpreactnativesdkexample/Pods-xmtpreactnativesdkexample-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; FD10A7F022414F080027D42C /* Start Packager */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -402,7 +402,7 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = ED9CBB83C05BBF316FC66508 /* Pods-xmtpreactnativesdkexample.debug.xcconfig */; + baseConfigurationReference = 6941C3A2B2A07289AC69DB6E /* Pods-xmtpreactnativesdkexample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -435,7 +435,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 74E1B7F7695132E36345D810 /* Pods-xmtpreactnativesdkexample.release.xcconfig */; + baseConfigurationReference = 62E6C2FC52DB7774BD32A867 /* Pods-xmtpreactnativesdkexample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index 1693eecb4..73c06c3fb 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -50,6 +50,51 @@ test('can make a MLS V3 client', async () => { return true }) +test('can create from key bundle with signer', 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', + enableV3: true, + dbEncryptionKey: keyBytes, + }) + + await alix.deleteLocalDatabase() + + // create a v2 client + const alix2 = await Client.create(alixWallet, { + env: 'local', + }) + + const keyBundle = await alix2.exportKeyBundle() + + // create from keybundle a v3 client + const alix3 = await Client.createFromKeyBundle( + keyBundle, + { + env: 'local', + appVersion: 'Testing/0.0.0', + enableV3: true, + dbEncryptionKey: keyBytes, + }, + alixWallet + ) + + const inboxState = await alix3.inboxState(true) + assert( + inboxState.installations.length === 2, + `installations length should be 2 but was ${inboxState.installations.length}` + ) + + 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, From 4431d29fd33a0e42ab83bc2abe1d2f9c6d43d47c Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 1 Nov 2024 14:17:55 -0700 Subject: [PATCH 5/7] fix: get iOS test passing --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 3 +- example/ios/Podfile.lock | 14 +++--- example/src/tests/groupTests.ts | 46 ++++++++----------- ios/XMTPModule.swift | 3 +- ios/XMTPReactNative.podspec | 2 +- src/lib/Client.ts | 2 +- src/lib/types/EventTypes.ts | 1 + 7 files changed, 32 insertions(+), 39 deletions(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 6b13181ec..f80211c73 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -246,6 +246,7 @@ class XMTPModule : Module() { "sign", "authed", "authedV3", + "bundleAuthed", "preCreateIdentityCallback", "preEnableIdentityCallback", "preAuthenticateToInboxCallback", @@ -431,7 +432,7 @@ class XMTPModule : Module() { clients[client.inboxId] = client ContentJson.Companion signer = null - sendEvent("authed", ClientWrapper.encodeToObj(client)) + sendEvent("bundleAuthed", ClientWrapper.encodeToObj(client)) } catch (e: Exception) { throw XMTPException("Failed to create client: $e") } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 36bec2102..80a196fcb 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -56,7 +56,7 @@ PODS: - hermes-engine/Pre-built (= 0.71.14) - hermes-engine/Pre-built (0.71.14) - libevent (2.1.12) - - LibXMTP (0.5.10) + - LibXMTP (0.6.0) - Logging (1.0.0) - MessagePacker (0.4.7) - MMKV (2.0.0): @@ -449,16 +449,16 @@ PODS: - GenericJSON (~> 2.0) - Logging (~> 1.0.0) - secp256k1.swift (~> 0.1) - - XMTP (0.16.0): + - XMTP (0.16.1): - Connect-Swift (= 0.12.0) - GzipSwift - - LibXMTP (= 0.5.10) + - LibXMTP (= 0.6.0) - web3.swift - XMTPReactNative (0.1.0): - ExpoModulesCore - MessagePacker - secp256k1.swift - - XMTP (= 0.16.0) + - XMTP (= 0.16.1) - Yoga (1.14.0) DEPENDENCIES: @@ -711,7 +711,7 @@ SPEC CHECKSUMS: GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - LibXMTP: 3b64b0b1e4157ff73c37cde60fe943f89e6f8693 + LibXMTP: 059c6d51b2c59419941ecff600aa586bbe083673 Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26 MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02 MMKV: f7d1d5945c8765f97f39c3d121f353d46735d801 @@ -763,8 +763,8 @@ SPEC CHECKSUMS: secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634 SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1 web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 - XMTP: 18d555dbf5afd3dcafa11b108042f9673da3c6b9 - XMTPReactNative: cd8be3d8547d116005f3d0f4f207f19c7b34d035 + XMTP: b2c2bcb0ddd6fbdb4820cac7be8a694c0f797425 + XMTPReactNative: 0b3b70a875bcb3defc24f051f69c35b257037c08 Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 PODFILE CHECKSUM: 0e6fe50018f34e575d38dc6a1fdf1f99c9596cdd diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index 73c06c3fb..6f2881185 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -50,8 +50,8 @@ test('can make a MLS V3 client', async () => { return true }) -test('can create from key bundle with signer', async () => { - const keyBytes = new Uint8Array([ +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, ]) @@ -75,7 +75,7 @@ test('can create from key bundle with signer', async () => { const keyBundle = await alix2.exportKeyBundle() // create from keybundle a v3 client - const alix3 = await Client.createFromKeyBundle( + const alixKeyBundle = await Client.createFromKeyBundle( keyBundle, { env: 'local', @@ -86,33 +86,23 @@ test('can create from key bundle with signer', async () => { alixWallet ) - const inboxState = await alix3.inboxState(true) + const inboxState = await alixKeyBundle.inboxState(true) assert( inboxState.installations.length === 2, `installations length should be 2 but was ${inboxState.installations.length}` ) - 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() - - const alix = await Client.create(alixWallet, { + const alix3 = await Client.create(alixWallet, { env: 'local', appVersion: 'Testing/0.0.0', enableV3: true, dbEncryptionKey: keyBytes, }) - const keyBundle = await alix.exportKeyBundle() + const keyBundle2 = await alix3.exportKeyBundle() - await Client.createFromKeyBundle( - keyBundle, + const alixKeyBundle2 = await Client.createFromKeyBundle( + keyBundle2, { env: 'local', appVersion: 'Testing/0.0.0', @@ -122,31 +112,31 @@ test('can revoke all other installations', async () => { alixWallet ) - await alix.deleteLocalDatabase() + await alix3.deleteLocalDatabase() - const alix2 = await Client.create(alixWallet, { + const alix4 = await Client.create(alixWallet, { env: 'local', appVersion: 'Testing/0.0.0', enableV3: true, dbEncryptionKey: keyBytes, }) - const inboxState = await alix2.inboxState(true) + const inboxState2 = await alix4.inboxState(true) assert( - inboxState.installations.length === 2, - `installations length should be 2 but was ${inboxState.installations.length}` + inboxState2.installations.length === 3, + `installations length should be 3 but was ${inboxState2.installations.length}` ) - await alix2.revokeAllOtherInstallations(alixWallet) + await alix4.revokeAllOtherInstallations(alixWallet) - const inboxState2 = await alix2.inboxState(true) + const inboxState3 = await alix4.inboxState(true) assert( - inboxState2.installations.length === 1, - `installations length should be 1 but was ${inboxState2.installations.length}` + inboxState3.installations.length === 1, + `installations length should be 1 but was ${inboxState3.installations.length}` ) assert( - inboxState2.installations[0].createdAt !== undefined, + inboxState3.installations[0].createdAt !== undefined, `installations createdAt should not be undefined` ) return true diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index d3c954cb0..a869a8309 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -105,6 +105,7 @@ public class XMTPModule: Module { "sign", "authed", "authedV3", + "bundleAuthed", "preCreateIdentityCallback", "preEnableIdentityCallback", "preAuthenticateToInboxCallback", @@ -315,7 +316,7 @@ public class XMTPModule: Module { let client = try await Client.from(v1Bundle: bundle.v1, options: options, signingKey: signer) await clientsManager.updateClient(key: client.inboxID, client: client) self.signer = nil - self.sendEvent("authed", try ClientWrapper.encodeToObj(client)) + self.sendEvent("bundleAuthed", try ClientWrapper.encodeToObj(client)) } catch { print("ERROR! Failed to create client: \(error)") throw error diff --git a/ios/XMTPReactNative.podspec b/ios/XMTPReactNative.podspec index d8cbfd215..4a4097fc5 100644 --- a/ios/XMTPReactNative.podspec +++ b/ios/XMTPReactNative.podspec @@ -26,5 +26,5 @@ Pod::Spec.new do |s| s.source_files = "**/*.{h,m,swift}" s.dependency 'secp256k1.swift' s.dependency "MessagePacker" - s.dependency "XMTP", "= 0.16.0" + s.dependency "XMTP", "= 0.16.1" end diff --git a/src/lib/Client.ts b/src/lib/Client.ts index 37ac433da..5f2d6c2a6 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -289,7 +289,7 @@ export class Client< ) this.authSubscription = XMTPModule.emitter.addListener( - 'authed', + 'bundleAuthed', async (message: { inboxId: string address: string diff --git a/src/lib/types/EventTypes.ts b/src/lib/types/EventTypes.ts index d9730ba2b..d02e22966 100644 --- a/src/lib/types/EventTypes.ts +++ b/src/lib/types/EventTypes.ts @@ -3,6 +3,7 @@ export enum EventTypes { Sign = 'sign', Authed = 'authed', AuthedV3 = 'authedV3', + BundleAuthed = 'bundleAuthed', PreCreateIdentityCallback = 'preCreateIdentityCallback', PreEnableIdentityCallback = 'preEnableIdentityCallback', // Conversations Events From 8447c73c2c123261519007e2a33d17d8335d41bb Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 1 Nov 2024 14:40:41 -0700 Subject: [PATCH 6/7] fix up client creation --- src/lib/Client.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/Client.ts b/src/lib/Client.ts index 5f2d6c2a6..abadeeb13 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -281,6 +281,7 @@ export class Client< await XMTPModule.receiveSignature(request.id, signature) } catch (e) { + this.removeAllSubscriptions() const errorMessage = 'ERROR in create. User rejected signature' console.info(errorMessage, e) reject(errorMessage) @@ -296,6 +297,7 @@ export class Client< installationId: string dbPath: string }) => { + this.removeAllSubscriptions() resolve( new Client( message.address, @@ -318,6 +320,7 @@ export class Client< options.historySyncUrl ) })().catch((error) => { + this.removeAllSubscriptions() console.error('ERROR in create: ', error) }) }) From 99194d659db70dbe5a564027941f1c087f61bff9 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 1 Nov 2024 14:41:55 -0700 Subject: [PATCH 7/7] fix up the android test --- example/src/tests/groupTests.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index 6f2881185..579ecd875 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -2339,19 +2339,11 @@ test('can sync all groups', async () => { } // First syncAllGroups after removal will still sync each group to set group inactive - // For some reason on Android (RN only), first syncAllGroups already returns 0 const numGroupsSynced2 = await bo.conversations.syncAllGroups() - if (Platform.OS === 'ios') { - assert( - numGroupsSynced2 === 50, - `should have synced 50 groups but synced ${numGroupsSynced2}` - ) - } else { - assert( - numGroupsSynced2 === 0, - `should have synced 0 groups but synced ${numGroupsSynced2}` - ) - } + assert( + numGroupsSynced2 === 50, + `should have synced 50 groups but synced ${numGroupsSynced2}` + ) // Next syncAllGroups will not sync inactive groups const numGroupsSynced3 = await bo.conversations.syncAllGroups()