@turnkey/core 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/__clients__/core.d.ts +10 -5
  2. package/dist/__clients__/core.d.ts.map +1 -1
  3. package/dist/__clients__/core.js +94 -37
  4. package/dist/__clients__/core.js.map +1 -1
  5. package/dist/__clients__/core.mjs +95 -38
  6. package/dist/__clients__/core.mjs.map +1 -1
  7. package/dist/__generated__/sdk-client-base.d.ts +12 -0
  8. package/dist/__generated__/sdk-client-base.d.ts.map +1 -1
  9. package/dist/__generated__/sdk-client-base.js +325 -204
  10. package/dist/__generated__/sdk-client-base.js.map +1 -1
  11. package/dist/__generated__/sdk-client-base.mjs +325 -204
  12. package/dist/__generated__/sdk-client-base.mjs.map +1 -1
  13. package/dist/__generated__/version.d.ts +1 -1
  14. package/dist/__generated__/version.js +1 -1
  15. package/dist/__generated__/version.mjs +1 -1
  16. package/dist/__inputs__/public_api.types.d.ts +296 -6
  17. package/dist/__inputs__/public_api.types.d.ts.map +1 -1
  18. package/dist/__types__/external-wallets.d.ts +19 -0
  19. package/dist/__types__/external-wallets.d.ts.map +1 -1
  20. package/dist/__types__/method-types.d.ts +7 -1
  21. package/dist/__types__/method-types.d.ts.map +1 -1
  22. package/dist/__wallet__/mobile/manager.d.ts.map +1 -1
  23. package/dist/__wallet__/mobile/manager.js +5 -2
  24. package/dist/__wallet__/mobile/manager.js.map +1 -1
  25. package/dist/__wallet__/mobile/manager.mjs +5 -2
  26. package/dist/__wallet__/mobile/manager.mjs.map +1 -1
  27. package/dist/__wallet__/wallet-connect/base.d.ts +25 -11
  28. package/dist/__wallet__/wallet-connect/base.d.ts.map +1 -1
  29. package/dist/__wallet__/wallet-connect/base.js +112 -30
  30. package/dist/__wallet__/wallet-connect/base.js.map +1 -1
  31. package/dist/__wallet__/wallet-connect/base.mjs +112 -30
  32. package/dist/__wallet__/wallet-connect/base.mjs.map +1 -1
  33. package/dist/__wallet__/wallet-connect/client.d.ts.map +1 -1
  34. package/dist/__wallet__/wallet-connect/client.js +4 -0
  35. package/dist/__wallet__/wallet-connect/client.js.map +1 -1
  36. package/dist/__wallet__/wallet-connect/client.mjs +4 -0
  37. package/dist/__wallet__/wallet-connect/client.mjs.map +1 -1
  38. package/dist/__wallet__/web/manager.d.ts +11 -0
  39. package/dist/__wallet__/web/manager.d.ts.map +1 -1
  40. package/dist/__wallet__/web/manager.js +36 -21
  41. package/dist/__wallet__/web/manager.js.map +1 -1
  42. package/dist/__wallet__/web/manager.mjs +36 -21
  43. package/dist/__wallet__/web/manager.mjs.map +1 -1
  44. package/dist/__wallet__/web/native/ethereum.d.ts.map +1 -1
  45. package/dist/__wallet__/web/native/ethereum.js +27 -4
  46. package/dist/__wallet__/web/native/ethereum.js.map +1 -1
  47. package/dist/__wallet__/web/native/ethereum.mjs +27 -4
  48. package/dist/__wallet__/web/native/ethereum.mjs.map +1 -1
  49. package/package.json +8 -8
@@ -2,7 +2,7 @@ import { TurnkeySDKClientBase } from '../__generated__/sdk-client-base.mjs';
2
2
  import { TurnkeyErrorCodes, TurnkeyError, AuthAction } from '@turnkey/sdk-types';
3
3
  import { DEFAULT_SESSION_EXPIRATION_IN_SECONDS } from '../__types__/auth.mjs';
4
4
  import { SessionKey, StamperType, WalletSource, Chain, FilterType, OtpTypeToFilterTypeMap, OtpType, Curve, SignIntent } from '../__types__/enums.mjs';
5
- import { withTurnkeyErrorHandling, isValidPasskeyName, isWeb, isReactNative, buildSignUpBody, findWalletProviderFromAddress, getPublicKeyFromStampHeader, addressFromPublicKey, getCurveTypeFromProvider, sendSignedRequest, fetchAllWalletAccountsWithCursor, mapAccountsToWallet, toExternalTimestamp, getAuthenticatorAddresses, isEthereumProvider, isSolanaProvider, getActiveSessionOrThrowIfRequired, getHashFunction, getEncodingType, getEncodedMessage, splitSignature, broadcastTransaction, getPolicySignature, googleISS, isWalletAccountArray, generateWalletAccountsFromAddressFormat } from '../utils.mjs';
5
+ import { withTurnkeyErrorHandling, isValidPasskeyName, isWeb, isReactNative, buildSignUpBody, findWalletProviderFromAddress, getPublicKeyFromStampHeader, addressFromPublicKey, getCurveTypeFromProvider, sendSignedRequest, getAuthenticatorAddresses, fetchAllWalletAccountsWithCursor, mapAccountsToWallet, toExternalTimestamp, isEthereumProvider, isSolanaProvider, getActiveSessionOrThrowIfRequired, getHashFunction, getEncodingType, getEncodedMessage, splitSignature, broadcastTransaction, getPolicySignature, googleISS, isWalletAccountArray, generateWalletAccountsFromAddressFormat } from '../utils.mjs';
6
6
  import { createStorageManager } from '../__storage__/base.mjs';
7
7
  import { CrossPlatformApiKeyStamper } from '../__stampers__/api/base.mjs';
8
8
  import { CrossPlatformPasskeyStamper } from '../__stampers__/passkey/base.mjs';
@@ -228,10 +228,10 @@ class TurnkeyClient {
228
228
  * - Stores the resulting session token and manages cleanup of unused key pairs.
229
229
  *
230
230
  * @param params.passkeyDisplayName - display name for the passkey (defaults to a generated name based on the current timestamp).
231
+ * @param params.challenge - challenge string to use for passkey registration. If not provided, a new challenge will be generated.
232
+ * @param params.expirationSeconds - session expiration time in seconds (defaults to the configured default).
231
233
  * @param params.createSubOrgParams - parameters for creating a sub-organization (e.g., authenticators, user metadata).
232
234
  * @param params.sessionKey - session key to use for storing the session (defaults to the default session key).
233
- * @param params.expirationSeconds - session expiration time in seconds (defaults to the configured default).
234
- * @param params.challenge - challenge string to use for passkey registration. If not provided, a new challenge will be generated.
235
235
  * @param params.organizationId - organization ID to target (defaults to the session's organization ID or the parent organization ID).
236
236
  * @returns A promise that resolves to a {@link PasskeyAuthResult}, which includes:
237
237
  * - `sessionToken`: the signed JWT session token.
@@ -239,7 +239,7 @@ class TurnkeyClient {
239
239
  * @throws {TurnkeyError} If there is an error during passkey creation, sub-organization creation, or session storage.
240
240
  */
241
241
  this.signUpWithPasskey = async (params) => {
242
- const { createSubOrgParams, passkeyDisplayName, sessionKey = SessionKey.DefaultSessionkey, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, organizationId, } = params || {};
242
+ const { passkeyDisplayName, challenge, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, createSubOrgParams, sessionKey = SessionKey.DefaultSessionkey, organizationId, } = params || {};
243
243
  let generatedPublicKey = undefined;
244
244
  return withTurnkeyErrorHandling(async () => {
245
245
  generatedPublicKey = await this.apiKeyStamper?.createKeyPair();
@@ -247,7 +247,7 @@ class TurnkeyClient {
247
247
  // A passkey will be created automatically when you call this function. The name is passed in
248
248
  const passkey = await this.createPasskey({
249
249
  name: passkeyName,
250
- ...(params?.challenge && { challenge: params.challenge }),
250
+ ...(challenge && { challenge }),
251
251
  });
252
252
  if (!passkey) {
253
253
  throw new TurnkeyError("Failed to create passkey: encoded challenge or attestation is missing", TurnkeyErrorCodes.INTERNAL_ERROR);
@@ -285,11 +285,13 @@ class TurnkeyClient {
285
285
  organizationId: organizationId ?? this.config.organizationId,
286
286
  expirationSeconds,
287
287
  });
288
- await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
289
- await this.storeSession({
290
- sessionToken: sessionResponse.session,
291
- sessionKey,
292
- });
288
+ await Promise.all([
289
+ this.apiKeyStamper?.deleteKeyPair(generatedPublicKey),
290
+ this.storeSession({
291
+ sessionToken: sessionResponse.session,
292
+ sessionKey,
293
+ }),
294
+ ]);
293
295
  generatedPublicKey = undefined; // Key pair was successfully used, set to null to prevent cleanup
294
296
  return {
295
297
  sessionToken: sessionResponse.session,
@@ -651,11 +653,13 @@ class TurnkeyClient {
651
653
  organizationId: this.config.organizationId,
652
654
  expirationSeconds,
653
655
  });
654
- await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
655
- await this.storeSession({
656
- sessionToken: sessionResponse.session,
657
- sessionKey,
658
- });
656
+ await Promise.all([
657
+ this.apiKeyStamper?.deleteKeyPair(generatedPublicKey),
658
+ this.storeSession({
659
+ sessionToken: sessionResponse.session,
660
+ sessionKey,
661
+ }),
662
+ ]);
659
663
  generatedPublicKey = undefined; // Key pair was successfully used, set to null to prevent cleanup
660
664
  // TODO (Moe): What happens if a user connects to MetaMask on Ethereum,
661
665
  // then switches to a Solana account within MetaMask? Will this flow break?
@@ -1033,16 +1037,17 @@ class TurnkeyClient {
1033
1037
  * @param params.oidcToken - OIDC token received after successful authentication with the OAuth provider.
1034
1038
  * @param params.publicKey - public key to use for authentication. Must be generated prior to calling this function, this is because the OIDC nonce has to be set to `sha256(publicKey)`.
1035
1039
  * @param params.providerName - name of the OAuth provider (defaults to a generated name with a timestamp).
1036
- * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
1037
- * @param params.invalidateExisting - flag to invalidate existing sessions for the user.
1038
1040
  * @param params.createSubOrgParams - parameters for sub-organization creation (e.g., authenticators, user metadata).
1041
+ * @param params.invalidateExisting - flag to invalidate existing sessions for the user.
1042
+ * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
1043
+ *
1039
1044
  * @returns A promise that resolves to an object containing:
1040
1045
  * - `sessionToken`: the signed JWT session token.
1041
1046
  * - `action`: whether the flow resulted in a login or signup ({@link AuthAction}).
1042
1047
  * @throws {TurnkeyError} If there is an error during the OAuth completion process, such as account lookup, sign-up, or login.
1043
1048
  */
1044
1049
  this.completeOauth = async (params) => {
1045
- const { oidcToken, publicKey, createSubOrgParams, providerName = "OpenID Connect Provider" + " " + Date.now(), sessionKey = SessionKey.DefaultSessionkey, invalidateExisting = false, } = params;
1050
+ const { oidcToken, publicKey, providerName, createSubOrgParams, invalidateExisting, sessionKey, } = params;
1046
1051
  return withTurnkeyErrorHandling(async () => {
1047
1052
  const accountRes = await this.httpClient.proxyGetAccount({
1048
1053
  filterType: "OIDC_TOKEN",
@@ -1056,8 +1061,8 @@ class TurnkeyClient {
1056
1061
  const loginRes = await this.loginWithOauth({
1057
1062
  oidcToken,
1058
1063
  publicKey,
1059
- invalidateExisting,
1060
- sessionKey,
1064
+ ...(invalidateExisting && { invalidateExisting }),
1065
+ ...(sessionKey && { sessionKey }),
1061
1066
  });
1062
1067
  return {
1063
1068
  ...loginRes,
@@ -1068,11 +1073,14 @@ class TurnkeyClient {
1068
1073
  const signUpRes = await this.signUpWithOauth({
1069
1074
  oidcToken,
1070
1075
  publicKey,
1071
- providerName,
1072
- sessionKey,
1076
+ ...(providerName && {
1077
+ providerName,
1078
+ }),
1073
1079
  ...(createSubOrgParams && {
1074
1080
  createSubOrgParams,
1075
1081
  }),
1082
+ ...(invalidateExisting && { invalidateExisting }),
1083
+ ...(sessionKey && { sessionKey }),
1076
1084
  });
1077
1085
  return {
1078
1086
  ...signUpRes,
@@ -1093,7 +1101,10 @@ class TurnkeyClient {
1093
1101
  * - Handles cleanup of unused key pairs if login fails.
1094
1102
  *
1095
1103
  * @param params.oidcToken - OIDC token received after successful authentication with the OAuth provider.
1096
- * @param params.publicKey - public key to use for authentication. Must be generated prior to calling this function.
1104
+ * @param params.publicKey - The public key bound to the login session. This key is required because it is directly
1105
+ * tied to the nonce used during OIDC token generation and must match the value
1106
+ * encoded in the token.
1107
+ * @param params.organizationId - ID of the organization to target when creating the session.
1097
1108
  * @param params.invalidateExisting - flag to invalidate existing sessions for the user.
1098
1109
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
1099
1110
  * @returns A promise that resolves to a {@link BaseAuthResult}, which includes:
@@ -1101,7 +1112,7 @@ class TurnkeyClient {
1101
1112
  * @throws {TurnkeyError} If there is an error during the OAuth login process or if key pair cleanup fails.
1102
1113
  */
1103
1114
  this.loginWithOauth = async (params) => {
1104
- const { oidcToken, invalidateExisting = false, publicKey, sessionKey = SessionKey.DefaultSessionkey, } = params;
1115
+ const { oidcToken, publicKey, organizationId, invalidateExisting = false, sessionKey = SessionKey.DefaultSessionkey, } = params;
1105
1116
  return withTurnkeyErrorHandling(async () => {
1106
1117
  if (!publicKey) {
1107
1118
  throw new TurnkeyError("Public key must be provided to log in with OAuth. Please create a key pair first.", TurnkeyErrorCodes.MISSING_PARAMS);
@@ -1110,6 +1121,7 @@ class TurnkeyClient {
1110
1121
  oidcToken,
1111
1122
  publicKey,
1112
1123
  invalidateExisting,
1124
+ ...(organizationId && { organizationId }),
1113
1125
  });
1114
1126
  if (!loginRes) {
1115
1127
  throw new TurnkeyError(`Auth proxy OAuth login failed`, TurnkeyErrorCodes.OAUTH_LOGIN_ERROR);
@@ -1162,7 +1174,7 @@ class TurnkeyClient {
1162
1174
  * @throws {TurnkeyError} If there is an error during the OAuth sign-up or login process.
1163
1175
  */
1164
1176
  this.signUpWithOauth = async (params) => {
1165
- const { oidcToken, publicKey, providerName, createSubOrgParams, sessionKey, } = params;
1177
+ const { oidcToken, publicKey, providerName = "OpenID Connect Provider" + " " + Date.now(), createSubOrgParams, sessionKey, } = params;
1166
1178
  return withTurnkeyErrorHandling(async () => {
1167
1179
  const signUpBody = buildSignUpBody({
1168
1180
  createSubOrgParams: {
@@ -1223,20 +1235,41 @@ class TurnkeyClient {
1223
1235
  }
1224
1236
  return withTurnkeyErrorHandling(async () => {
1225
1237
  let embedded = [];
1238
+ const organizationId = organizationIdFromParams || session?.organizationId;
1239
+ const userId = userIdFromParams || session?.userId;
1240
+ // we start fetching user early if we have the required params (needed for connected wallets)
1241
+ // this runs in parallel with the embedded wallet fetching below
1242
+ let userPromise;
1243
+ if (organizationId && userId && this.walletManager?.connector) {
1244
+ const signedUserRequest = await this.httpClient.stampGetUser({
1245
+ userId,
1246
+ organizationId,
1247
+ }, stampWith);
1248
+ if (!signedUserRequest) {
1249
+ throw new TurnkeyError("Failed to stamp user request", TurnkeyErrorCodes.INVALID_REQUEST);
1250
+ }
1251
+ userPromise = sendSignedRequest(signedUserRequest).then((response) => getAuthenticatorAddresses(response.user));
1252
+ }
1226
1253
  // if connectedOnly is true, we skip fetching embedded wallets
1227
1254
  if (!connectedOnly) {
1228
- const organizationId = organizationIdFromParams || session?.organizationId;
1229
1255
  if (!organizationId) {
1230
1256
  throw new TurnkeyError("No organization ID provided and no active session found. Please log in first or pass in an organization ID.", TurnkeyErrorCodes.INVALID_REQUEST);
1231
1257
  }
1232
- const userId = userIdFromParams || session?.userId;
1233
1258
  if (!userId) {
1234
1259
  throw new TurnkeyError("No user ID provided and no active session found. Please log in first or pass in a user ID.", TurnkeyErrorCodes.INVALID_REQUEST);
1235
1260
  }
1236
- const accounts = await fetchAllWalletAccountsWithCursor(this.httpClient, organizationId, stampWith);
1237
- const walletsRes = await this.httpClient.getWallets({
1261
+ // we stamp the wallet request first
1262
+ // this is done to avoid concurrent passkey prompts
1263
+ const signedWalletsRequest = await this.httpClient.stampGetWallets({
1238
1264
  organizationId,
1239
1265
  }, stampWith);
1266
+ if (!signedWalletsRequest) {
1267
+ throw new TurnkeyError("Failed to stamp wallet request", TurnkeyErrorCodes.INVALID_REQUEST);
1268
+ }
1269
+ const [accounts, walletsRes] = await Promise.all([
1270
+ fetchAllWalletAccountsWithCursor(this.httpClient, organizationId, stampWith),
1271
+ sendSignedRequest(signedWalletsRequest),
1272
+ ]);
1240
1273
  // create a map of walletId to EmbeddedWallet for easy lookup
1241
1274
  const walletMap = new Map(walletsRes.wallets.map((wallet) => [
1242
1275
  wallet.walletId,
@@ -1264,6 +1297,13 @@ class TurnkeyClient {
1264
1297
  group.push(provider);
1265
1298
  groupedProviders.set(walletId, group);
1266
1299
  }
1300
+ // we fetch user once for all connected wallets to avoid duplicate `fetchUser` calls
1301
+ // this is only done if we have `organizationId` and `userId`
1302
+ // Note: this was started earlier in parallel with embedded wallet fetching for performance
1303
+ let authenticatorAddresses;
1304
+ if (userPromise) {
1305
+ authenticatorAddresses = await userPromise;
1306
+ }
1267
1307
  // has to be done in a for of loop so we can await each fetchWalletAccounts call individually
1268
1308
  // otherwise await Promise.all would cause them all to fire at once breaking passkey only set ups
1269
1309
  // (multiple wallet fetches at once causing "OperationError: A request is already pending.")
@@ -1288,6 +1328,7 @@ class TurnkeyClient {
1288
1328
  organizationId: organizationIdFromParams,
1289
1329
  }),
1290
1330
  ...(userIdFromParams !== undefined && { userId: userIdFromParams }),
1331
+ ...(authenticatorAddresses && { authenticatorAddresses }),
1291
1332
  });
1292
1333
  wallet.accounts = accounts;
1293
1334
  if (wallet.accounts.length > 0) {
@@ -1315,6 +1356,7 @@ class TurnkeyClient {
1315
1356
  * @param params.stampWith - parameter to stamp the request with a specific stamper (StamperType.Passkey, StamperType.ApiKey, or StamperType.Wallet).
1316
1357
  * @param params.organizationId - organization ID to target (defaults to the session's organization ID).
1317
1358
  * @param params.userId - user ID to target (defaults to the session's user ID).
1359
+ * @param params.authenticatorAddresses - optional authenticator addresses to avoid redundant user fetches (this is used for connected wallets to determine if a connected wallet is an authenticator)
1318
1360
  *
1319
1361
  * @returns A promise that resolves to an array of `v1WalletAccount` objects.
1320
1362
  * @throws {TurnkeyError} If no active session is found or if there is an error fetching wallet accounts.
@@ -1369,9 +1411,13 @@ class TurnkeyClient {
1369
1411
  const sign = this.walletManager.connector.sign.bind(this.walletManager.connector);
1370
1412
  let ethereumAddresses = [];
1371
1413
  let solanaAddresses = [];
1372
- // we only fetch the user if we have to the organizationId and userId
1373
- // if not that means `isAuthenticator` will always be false
1374
- if (organizationId && userId) {
1414
+ if (params.authenticatorAddresses) {
1415
+ ({ ethereum: ethereumAddresses, solana: solanaAddresses } =
1416
+ params.authenticatorAddresses);
1417
+ }
1418
+ else if (organizationId && userId) {
1419
+ // we only fetch the user if authenticator addresses aren't provided and we have the organizationId and userId
1420
+ // if not, then that means `isAuthenticator` will always be false
1375
1421
  const user = await this.fetchUser({
1376
1422
  userId,
1377
1423
  organizationId,
@@ -2695,8 +2741,10 @@ class TurnkeyClient {
2695
2741
  withTurnkeyErrorHandling(async () => {
2696
2742
  const session = await this.storageManager.getSession(sessionKey);
2697
2743
  if (session) {
2698
- await this.apiKeyStamper?.deleteKeyPair(session.publicKey);
2699
- await this.storageManager.clearSession(sessionKey);
2744
+ await Promise.all([
2745
+ this.apiKeyStamper?.deleteKeyPair(session.publicKey),
2746
+ this.storageManager.clearSession(sessionKey),
2747
+ ]);
2700
2748
  }
2701
2749
  else {
2702
2750
  throw new TurnkeyError(`No session found with key: ${sessionKey}`, TurnkeyErrorCodes.NOT_FOUND);
@@ -3071,15 +3119,24 @@ class TurnkeyClient {
3071
3119
  this.storageManager = await createStorageManager();
3072
3120
  // Initialize the API key stamper
3073
3121
  this.apiKeyStamper = new CrossPlatformApiKeyStamper(this.storageManager);
3074
- await this.apiKeyStamper.init();
3122
+ // we parallelize independent initializations:
3123
+ // - API key stamper init
3124
+ // - Passkey stamper creation and init (if configured)
3125
+ // - Wallet manager creation (if configured)
3126
+ const initTasks = [this.apiKeyStamper.init()];
3075
3127
  if (this.config.passkeyConfig) {
3076
- this.passkeyStamper = new CrossPlatformPasskeyStamper(this.config.passkeyConfig);
3077
- await this.passkeyStamper.init();
3128
+ const passkeyStamper = new CrossPlatformPasskeyStamper(this.config.passkeyConfig);
3129
+ initTasks.push(passkeyStamper.init().then(() => {
3130
+ this.passkeyStamper = passkeyStamper;
3131
+ }));
3078
3132
  }
3079
3133
  if (this.config.walletConfig?.features?.auth ||
3080
3134
  this.config.walletConfig?.features?.connecting) {
3081
- this.walletManager = await createWalletManager(this.config.walletConfig);
3135
+ initTasks.push(createWalletManager(this.config.walletConfig).then((manager) => {
3136
+ this.walletManager = manager;
3137
+ }));
3082
3138
  }
3139
+ await Promise.all(initTasks);
3083
3140
  // Initialize the HTTP client with the appropriate stampers
3084
3141
  // Note: not passing anything here since we want to use the configured stampers and this.config
3085
3142
  this.httpClient = this.createHttpClient();