Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to create from key bundle with signer #520

Merged
merged 7 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,34 @@ class XMTPModule : Module() {
}
}

AsyncFunction("createFromKeyBundleWithSigner") Coroutine { address: String, keyBundle: String, dbEncryptionKey: List<Int>?, 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<Int>?, authParams: String ->
withContext(Dispatchers.IO) {
logV("createV3")
Expand Down
70 changes: 35 additions & 35 deletions example/ios/xmtpreactnativesdkexample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand All @@ -36,15 +36,15 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = xmtpreactnativesdkexample/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = xmtpreactnativesdkexample/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = xmtpreactnativesdkexample/main.m; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
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 = "<group>"; };
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-xmtpreactnativesdkexample/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
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 = "<group>"; };
/* End PBXFileReference section */
Expand All @@ -54,7 +54,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9453E5BA4D9E60C6B21EAF55 /* libPods-xmtpreactnativesdkexample.a in Frameworks */,
54E6B19EA8330AD1B859A861 /* libPods-xmtpreactnativesdkexample.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -89,7 +89,7 @@
children = (
A6AE8C832A49F1F300BD4E8B /* libMessagePack.swift.a */,
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
A15D051C53AD0CEEBB4113F5 /* libPods-xmtpreactnativesdkexample.a */,
14DAE522F34BCEFD44E396D2 /* libPods-xmtpreactnativesdkexample.a */,
);
name = Frameworks;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 = "<group>";
Expand All @@ -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 = (
);
Expand Down Expand Up @@ -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 = (
Expand All @@ -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 = (
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
59 changes: 59 additions & 0 deletions example/src/tests/groupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,51 @@
return true
})

test('can create from key bundle with signer', async () => {
const keyBytes = new Uint8Array([

Check warning on line 54 in example/src/tests/groupTests.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
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,
Expand All @@ -63,6 +108,20 @@
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, {
Expand Down
10 changes: 10 additions & 0 deletions example/src/tests/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
})

Expand Down
25 changes: 25 additions & 0 deletions ios/XMTPModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
29 changes: 29 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
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,
Expand Down
Loading
Loading