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

RN multiple sessions #520

Merged
merged 21 commits into from
Mar 5, 2025
Merged

RN multiple sessions #520

merged 21 commits into from
Mar 5, 2025

Conversation

moeodeh3
Copy link
Contributor

@moeodeh3 moeodeh3 commented Feb 28, 2025

Summary & Motivation

This PR adds support for multiple sessions by allowing sessions to be created with a custom sessionKey instead of using a single default session. We also introduced the concept of a "selected session," meaning users can switch between sessions by calling setSelectedSession( {sessionKey: <key here>}) , which updates the client, user, and session info accordingly. Any function calls, like updateUser or createWallet, now apply to the selected session.

How I Tested These Changes

tested in a custom app using the expo-template :)

Did you add a changeset?

If updating one of our packages, you'll likely need to add a changeset to your PR. To do so, run pnpm changeset. pnpm changeset will generate a file where you should write a human friendly message about the changes. Note how this (example) includes the package name (should be auto added by the command) along with the type of semver change (major.minor.patch) (which you should set).

These changes will be used at release time to determine what packages to publish and how to bump their version. For more context see this comment.

Copy link

codesandbox-ci bot commented Feb 28, 2025

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@moeodeh3 moeodeh3 changed the title RN multiple sessions RN multiple sessions [wip] Feb 28, 2025
@moeodeh3 moeodeh3 force-pushed the moeO/multipleSessions branch from c1fc276 to 6bf5ed0 Compare March 2, 2025 00:39
Copy link
Contributor

@zkharit zkharit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple comments, HUGE improvement adding multiple sessions!!

},
"peerDependencies": {
"@types/react": "^18.2.75",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a wide range of versions for some of these dependencies, can be bit more explicit about which versions will be used, or is there some reason this range is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just did this to follow what we do in sdk-react. I believe because we only use react for hooks, any version above 16.8.0 is sufficient. But we don't want users to think we restrict other major releases so thats why we do this. I could be wrong though - maybe @moe-dev could comment on this. If this is unnecessary I can update both sdk-react and sdk-react-native

@@ -0,0 +1,275 @@
import * as Keychain from "react-native-keychain";
import {
TURNKEY_EMBEDDED_KEY_STORAGE,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand the flow correctly, we store a long lived api key at TURNKEY_EMBEDDED_KEY_STORAGE in the Keychain, store session keys in the Keychain in their respective "key" as well as in the key index and then store the selected session in the Keychain as well? It feels like the session index may be redundant and could introduce issues with synchronization? But maybe is there a specific reason we need the index that im missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the flow looks something like:

  1. createEmbeddedKey() - this generates an api key-pair and stores the private key in local storage under TURNKEY_EMBEDDED_KEY_STORAGE, and returns the public key
  2. user uses the public key (targetPublicKey) to make a request to turnkey
  3. you call createSession() with the credential bundle - this will pull the private key from TURNKEY_EMBEDDED_KEY_STORAGE (also deletes it), decrypt the bundle using the private key, and then saves the session under TURNKEY_DEFAULT_SESSION_STORAGE

so why do we currently store the selected session (TURNKEY_SELECTED_SESSION) and the session index (TURNKEY_SESSION_IDS_INDEX)?

for the useEffect (which runs on app initialization), basically if the user refreshes the app we would like to automatically select the session that was previously selected. Then for the reason we have the TURNKEY_SESSION_IDS_INDEX is because we don't know all the ids we store these sessions under (since the user can set it) so we need to know what service names to pull from in secure storage in the useEffect on app initialization.

I do see what you mean though - let me look into possible ways we can clean this up. Maybe there is a way to get all the service names of stuff in secure storage which would then allow us to remove TURNKEY_SESSION_IDS_INDEX.


/**
* Retrieves the stored embedded key from secure storage.
* Optionally deletes the key from storage after retrieval.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to explain this in writing, the key can be deleted from storage after retrieval because ultimately, the full on decrypted API key will be stored in secure storage, correct? in which case the credential bundle/embedded key are no longer needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct


- `turnkey-embedded-key`: Stores the private key that corresponds to the public key used when initiating the session request to Turnkey.
- `turnkey-session`: Stores the session credentials, including the private key, public key, and expiry time, which are decrypted from the credential bundle after a session is created.
Using multiple sessions can be beneficial when enabling different authentication methods for various operations. For example, you might authenticate a user with OTP for login while using a passkey-based session for signing transactions.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🏅

});
return new TurnkeyClient({ baseUrl: apiBaseUrl }, stamper);
};
config.onSessionSelected?.(selectedSession!);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

...(email?.trim() && { userEmail: email }),
};

const result = await client.updateUser({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

realized we're using the raw http Turnkey client here, instead of something akin to @turnkey/sdk-server which abstracts away the need to specify type/timestamp/org ID. this is def fine for now, but down the line I wonder if it'd help if we had our React Native SDK also have some similar abstractions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noted


await clearSelectedSessionKey();

setSession(undefined);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using undefined here is helpful — there could also be an argument made for just calling setSession(), without any arguments, but I think this works great too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setSession() is a state function - I don't think we can do that unless we write a helper function and call it inside. I think I would prefer setSession(undefined) but if you disagree I can change it


if (credentials) {
try {
keys = JSON.parse(credentials.password);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably overkill, but wouldn't be too bad to include a description on how Keychain.getGenericPassword() works. i.e. that we're really actually storing the sensitive data (session keys) under credentials.password , and that credentials.password doesn't actually have to be a literal password

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@moeodeh3 moeodeh3 merged commit 7a7e66e into main Mar 5, 2025
5 checks passed
@moeodeh3 moeodeh3 deleted the moeO/multipleSessions branch March 5, 2025 19:41
@moeodeh3 moeodeh3 changed the title RN multiple sessions [wip] RN multiple sessions Mar 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants