@turnkey/sdk-browser 1.16.0 → 3.0.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 (59) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/dist/__clients__/base-client.d.ts +7 -0
  3. package/dist/__clients__/base-client.d.ts.map +1 -0
  4. package/dist/__clients__/base-client.js +13 -0
  5. package/dist/__clients__/base-client.js.map +1 -0
  6. package/dist/__clients__/base-client.mjs +11 -0
  7. package/dist/__clients__/base-client.mjs.map +1 -0
  8. package/dist/__clients__/browser-clients.d.ts +251 -0
  9. package/dist/__clients__/browser-clients.d.ts.map +1 -0
  10. package/dist/__clients__/browser-clients.js +657 -0
  11. package/dist/__clients__/browser-clients.js.map +1 -0
  12. package/dist/__clients__/browser-clients.mjs +652 -0
  13. package/dist/__clients__/browser-clients.mjs.map +1 -0
  14. package/dist/__generated__/sdk-client-base.d.ts.map +1 -1
  15. package/dist/__generated__/sdk-client-base.js +110 -114
  16. package/dist/__generated__/sdk-client-base.js.map +1 -1
  17. package/dist/__generated__/sdk-client-base.mjs +110 -114
  18. package/dist/__generated__/sdk-client-base.mjs.map +1 -1
  19. package/dist/__generated__/sdk_api_types.d.ts +2 -2
  20. package/dist/__generated__/sdk_api_types.d.ts.map +1 -1
  21. package/dist/__generated__/version.d.ts +1 -1
  22. package/dist/__generated__/version.d.ts.map +1 -1
  23. package/dist/__generated__/version.js +1 -1
  24. package/dist/__generated__/version.mjs +1 -1
  25. package/dist/__inputs__/public_api.types.d.ts +77 -9
  26. package/dist/__inputs__/public_api.types.d.ts.map +1 -1
  27. package/dist/__types__/base.d.ts +93 -0
  28. package/dist/__types__/base.d.ts.map +1 -1
  29. package/dist/__types__/base.js +5 -0
  30. package/dist/__types__/base.js.map +1 -1
  31. package/dist/__types__/base.mjs +6 -1
  32. package/dist/__types__/base.mjs.map +1 -1
  33. package/dist/constants.d.ts +2 -0
  34. package/dist/constants.d.ts.map +1 -0
  35. package/dist/constants.js +6 -0
  36. package/dist/constants.js.map +1 -0
  37. package/dist/constants.mjs +4 -0
  38. package/dist/constants.mjs.map +1 -0
  39. package/dist/index.d.ts +3 -4
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +9 -4
  42. package/dist/index.js.map +1 -1
  43. package/dist/index.mjs +3 -2
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/models.d.ts +0 -53
  46. package/dist/models.d.ts.map +1 -1
  47. package/dist/sdk-client.d.ts +13 -186
  48. package/dist/sdk-client.d.ts.map +1 -1
  49. package/dist/sdk-client.js +75 -437
  50. package/dist/sdk-client.js.map +1 -1
  51. package/dist/sdk-client.mjs +69 -431
  52. package/dist/sdk-client.mjs.map +1 -1
  53. package/dist/storage.d.ts +15 -3
  54. package/dist/storage.d.ts.map +1 -1
  55. package/dist/storage.js +19 -0
  56. package/dist/storage.js.map +1 -1
  57. package/dist/storage.mjs +19 -1
  58. package/dist/storage.mjs.map +1 -1
  59. package/package.json +7 -6
@@ -0,0 +1,652 @@
1
+ import { getWebAuthnAttestation } from '@turnkey/http';
2
+ import { TurnkeyBaseClient } from './base-client.mjs';
3
+ import { SessionType, AuthClient } from '../__types__/base.mjs';
4
+ import { generateRandomBuffer, base64UrlEncode, createEmbeddedAPIKey } from '../utils.mjs';
5
+ import { storeSession, saveSession, getStorageValue, StorageKeys } from '../storage.mjs';
6
+ import { DEFAULT_SESSION_EXPIRATION_IN_SECONDS } from '../constants.mjs';
7
+
8
+ class TurnkeyBrowserClient extends TurnkeyBaseClient {
9
+ constructor(config, authClient) {
10
+ super(config, authClient);
11
+ this.login = async (config) => {
12
+ const readOnlySessionResult = await this.createReadOnlySession(config || {});
13
+ const session = {
14
+ sessionType: SessionType.READ_ONLY,
15
+ userId: readOnlySessionResult.userId,
16
+ organizationId: readOnlySessionResult.organizationId,
17
+ expiry: Number(readOnlySessionResult.sessionExpiry),
18
+ token: readOnlySessionResult.session,
19
+ };
20
+ await storeSession(session, this.authClient);
21
+ return readOnlySessionResult;
22
+ };
23
+ /**
24
+ * Attempts to refresh an existing Session. This method infers the current user's organization ID and target userId.
25
+ * This will use a passkeyStamper for `READ_ONLY` sessions or an `iframeStamper` for `READ_WRITE` sessions.
26
+ *
27
+ * @param RefreshSessionParams
28
+ * @param params.sessionType - The type of session that is being refreshed
29
+ * @param params.targetPublicKey - The public key of the target client
30
+ * @param params.expirationSeconds - Specify how long to extend the session. Defaults to 900 seconds or 15 minutes.
31
+ * @returns {Promise<void>}
32
+ */
33
+ this.refreshSession = async (params) => {
34
+ const { sessionType = SessionType.READ_WRITE, targetPublicKey, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, } = params;
35
+ try {
36
+ if (sessionType === SessionType.READ_ONLY) {
37
+ if (this instanceof TurnkeyPasskeyClient) {
38
+ throw new Error("You must use a passkey client to refresh a read session"); // TODO: support wallet client
39
+ }
40
+ const readOnlySessionResult = await this.createReadOnlySession({});
41
+ const session = {
42
+ sessionType: SessionType.READ_ONLY,
43
+ userId: readOnlySessionResult.userId,
44
+ organizationId: readOnlySessionResult.organizationId,
45
+ expiry: Number(readOnlySessionResult.sessionExpiry),
46
+ token: readOnlySessionResult.session,
47
+ };
48
+ await storeSession(session, AuthClient.Passkey);
49
+ }
50
+ else if (sessionType === SessionType.READ_WRITE) {
51
+ if (!targetPublicKey) {
52
+ throw new Error("You must provide a targetPublicKey to refresh a read-write session.");
53
+ }
54
+ const readWriteSessionResult = await this.createReadWriteSession({
55
+ targetPublicKey,
56
+ expirationSeconds,
57
+ invalidateExisting: true,
58
+ });
59
+ const session = {
60
+ sessionType: SessionType.READ_WRITE,
61
+ userId: readWriteSessionResult.userId,
62
+ organizationId: readWriteSessionResult.organizationId,
63
+ expiry: Date.now() + Number(expirationSeconds) * 1000,
64
+ token: readWriteSessionResult.credentialBundle,
65
+ };
66
+ if (this instanceof TurnkeyIframeClient) {
67
+ await this.injectCredentialBundle(session.token);
68
+ }
69
+ else {
70
+ // Throw an error if the client is not an iframe client
71
+ throw new Error("You must use an iframe client to refresh a read-write session");
72
+ }
73
+ await storeSession(session, AuthClient.Iframe);
74
+ }
75
+ else {
76
+ throw new Error(`Invalid session type passed: ${sessionType}`);
77
+ }
78
+ }
79
+ catch (error) {
80
+ throw new Error(`Unable to refresh session: ${error}`);
81
+ }
82
+ };
83
+ /**
84
+ * Log in with a bundle. This method uses a bundle sent to the end user email
85
+ * To be used in conjunction with an `iframeStamper`.
86
+ *
87
+ * @param LoginWithBundleParams
88
+ * @param params.bundle - Credential bundle to log in with
89
+ * @param params.expirationSeconds - Expiration time for the session in seconds. Defaults to 900 seconds or 15 minutes.
90
+ * @returns {Promise<void>}
91
+ */
92
+ this.loginWithBundle = async (params) => {
93
+ const { bundle, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, } = params;
94
+ if (this instanceof TurnkeyIframeClient) {
95
+ await this.injectCredentialBundle(bundle);
96
+ }
97
+ else {
98
+ // Throw an error if the client is not an iframe client
99
+ throw new Error("You must use an iframe client to log in with a session."); //should we default to a "localStorage" client?
100
+ }
101
+ const whoAmI = await this.getWhoami();
102
+ const session = {
103
+ sessionType: SessionType.READ_WRITE,
104
+ userId: whoAmI.userId,
105
+ organizationId: whoAmI.organizationId,
106
+ expiry: Date.now() + Number(expirationSeconds) * 1000,
107
+ token: bundle,
108
+ };
109
+ await storeSession(session, AuthClient.Iframe);
110
+ };
111
+ /**
112
+ * Log in with a session object. This method uses a session object from server actions and stores it and the active client in local storage
113
+ * To be used in conjunction with an `iframeStamper`.
114
+ *
115
+ * @param session
116
+ * @returns {Promise<void>}
117
+ */
118
+ this.loginWithSession = async (session) => {
119
+ if (this instanceof TurnkeyIframeClient) {
120
+ await this.injectCredentialBundle(session.token);
121
+ }
122
+ else {
123
+ // Throw an error if the client is not an iframe client
124
+ throw new Error("You must use an iframe client to log in with a session."); //should we default to a "localStorage" client?
125
+ }
126
+ await storeSession(session, AuthClient.Iframe);
127
+ };
128
+ /**
129
+ * Log in with a passkey.
130
+ * To be used in conjunction with a `passkeyStamper`
131
+ *
132
+ * @param LoginWithPasskeyParams
133
+ * @param params.sessionType - The type of session to create
134
+ * @param params.iframeClient - The iframe client to use to inject the credential bundle
135
+ * @param params.targetPublicKey - The public key of the target client
136
+ * @param params.expirationSeconds - Expiration time for the session in seconds. Defaults to 900 seconds or 15 minutes.
137
+ * @returns {Promise<void>}
138
+ */
139
+ this.loginWithPasskey = async (params) => {
140
+ try {
141
+ const { sessionType = SessionType.READ_WRITE, iframeClient, targetPublicKey, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, } = params;
142
+ // Create a read-only session
143
+ if (sessionType === SessionType.READ_ONLY) {
144
+ const readOnlySessionResult = await this.createReadOnlySession({});
145
+ const session = {
146
+ sessionType: SessionType.READ_ONLY,
147
+ userId: readOnlySessionResult.userId,
148
+ organizationId: readOnlySessionResult.organizationId,
149
+ expiry: Number(readOnlySessionResult.sessionExpiry),
150
+ token: readOnlySessionResult.session,
151
+ };
152
+ await storeSession(session, AuthClient.Passkey);
153
+ // Create a read-write session
154
+ }
155
+ else if (sessionType === SessionType.READ_WRITE) {
156
+ if (!targetPublicKey) {
157
+ throw new Error("You must provide a targetPublicKey to create a read-write session.");
158
+ }
159
+ const readWriteSessionResult = await this.createReadWriteSession({
160
+ targetPublicKey,
161
+ expirationSeconds,
162
+ });
163
+ const session = {
164
+ sessionType: SessionType.READ_WRITE,
165
+ userId: readWriteSessionResult.userId,
166
+ organizationId: readWriteSessionResult.organizationId,
167
+ expiry: Date.now() + Number(expirationSeconds) * 1000,
168
+ token: readWriteSessionResult.credentialBundle,
169
+ };
170
+ if (!iframeClient) {
171
+ throw new Error("You must provide an iframe client to log in with a passkey.");
172
+ }
173
+ await iframeClient.injectCredentialBundle(session.token);
174
+ await storeSession(session, AuthClient.Iframe);
175
+ }
176
+ else {
177
+ throw new Error(`Invalid session type passed: ${sessionType}`);
178
+ }
179
+ }
180
+ catch (error) {
181
+ throw new Error(`Unable to log in with the provided passkey: ${error}`);
182
+ }
183
+ };
184
+ /**
185
+ * Log in with a browser wallet.
186
+ *
187
+ * @param LoginWithWalletParams
188
+ * @param params.sessionType - The type of session to create
189
+ * @param params.iframeClient - The iframe client to use to inject the credential bundle
190
+ * @param params.targetPublicKey - The public key of the target iframe
191
+ * @param params.expirationSeconds - The expiration time for the session in seconds
192
+ * @returns {Promise<void>}
193
+ */
194
+ this.loginWithWallet = async (params) => {
195
+ try {
196
+ const { sessionType = SessionType.READ_WRITE, iframeClient, targetPublicKey, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, } = params;
197
+ // Create a read-only session
198
+ if (sessionType === SessionType.READ_ONLY) {
199
+ const readOnlySessionResult = await this.createReadOnlySession({});
200
+ const session = {
201
+ sessionType: SessionType.READ_ONLY,
202
+ userId: readOnlySessionResult.userId,
203
+ organizationId: readOnlySessionResult.organizationId,
204
+ expiry: Number(readOnlySessionResult.sessionExpiry),
205
+ token: readOnlySessionResult.session,
206
+ };
207
+ await storeSession(session, AuthClient.Wallet);
208
+ // Create a read-write session
209
+ }
210
+ else if (sessionType === SessionType.READ_WRITE) {
211
+ if (!targetPublicKey) {
212
+ throw new Error("You must provide a targetPublicKey to create a read-write session.");
213
+ }
214
+ const readWriteSessionResult = await this.createReadWriteSession({
215
+ targetPublicKey,
216
+ expirationSeconds,
217
+ });
218
+ const session = {
219
+ sessionType: SessionType.READ_WRITE,
220
+ userId: readWriteSessionResult.userId,
221
+ organizationId: readWriteSessionResult.organizationId,
222
+ expiry: Date.now() + Number(expirationSeconds) * 1000,
223
+ token: readWriteSessionResult.credentialBundle,
224
+ };
225
+ if (!iframeClient) {
226
+ throw new Error("You must provide an iframe client to log in with a wallet.");
227
+ }
228
+ await iframeClient.injectCredentialBundle(session.token);
229
+ await storeSession(session, AuthClient.Iframe);
230
+ }
231
+ else {
232
+ throw new Error(`Invalid session type passed: ${sessionType}`);
233
+ }
234
+ }
235
+ catch (error) {
236
+ throw new Error(`Unable to log in with the provided wallet: ${error}`);
237
+ }
238
+ };
239
+ /**
240
+ * Creates a read-write session. This method infers the current user's organization ID and target userId.
241
+ * To be used in conjunction with an `iframeStamper`: the resulting session's credential bundle can be
242
+ * injected into an iframeStamper to create a session that enables both read and write requests.
243
+ *
244
+ * @param targetEmbeddedKey
245
+ * @param expirationSeconds
246
+ * @param userId
247
+ * @returns {Promise<SdkApiTypes.TCreateReadWriteSessionResponse>}
248
+ */
249
+ this.loginWithReadWriteSession = async (targetEmbeddedKey, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, userId) => {
250
+ try {
251
+ const readWriteSessionResult = await this.createReadWriteSession({
252
+ targetPublicKey: targetEmbeddedKey,
253
+ expirationSeconds,
254
+ userId: userId,
255
+ });
256
+ // Ensure session and sessionExpiry are included in the object
257
+ const readWriteSessionResultWithSession = {
258
+ ...readWriteSessionResult,
259
+ credentialBundle: readWriteSessionResult.credentialBundle,
260
+ sessionExpiry: Date.now() + Number(expirationSeconds) * 1000,
261
+ };
262
+ // store auth bundle in local storage
263
+ await saveSession(readWriteSessionResultWithSession, this.authClient);
264
+ return readWriteSessionResultWithSession;
265
+ }
266
+ catch (error) {
267
+ throw new Error(`Unable to log in with the provided read-write session: ${error}`);
268
+ }
269
+ };
270
+ /**
271
+ * Logs in with an existing auth bundle. this bundle enables both read and write requests.
272
+ *
273
+ * @param credentialBundle
274
+ * @param expirationSeconds
275
+ * @returns {Promise<boolean>}
276
+ */
277
+ this.loginWithAuthBundle = async (credentialBundle, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS) => {
278
+ try {
279
+ const whoAmIResult = await this.getWhoami();
280
+ const readWriteSessionResultWithSession = {
281
+ ...whoAmIResult,
282
+ credentialBundle: credentialBundle,
283
+ sessionExpiry: Date.now() + Number(expirationSeconds) * 1000,
284
+ };
285
+ await saveSession(readWriteSessionResultWithSession, this.authClient);
286
+ return true;
287
+ }
288
+ catch (error) {
289
+ throw new Error(`Unable to log in with the provided auth bundle: ${error}`);
290
+ }
291
+ };
292
+ /**
293
+ * Removes authentication factors from an end user.
294
+ *
295
+ * This function allows selectively removing:
296
+ * - Phone number
297
+ * - Email
298
+ * - Authenticators (by ID)
299
+ * - OAuth providers (by ID)
300
+ * - API keys (by ID)
301
+ *
302
+ * All removal operations are executed in parallel if multiple
303
+ * parameters are provided.
304
+ *
305
+ * @param params - A structured object containing all the removal parameters
306
+ * @param params.userId - Unique identifier of the user
307
+ * @param params.phoneNumber - true to remove the phone number
308
+ * @param params.email - true to remove the email
309
+ * @param params.authenticatorIds - Array of authenticator IDs to remove
310
+ * @param params.oauthProviderIds - Array of OAuth provider IDs to remove
311
+ * @param params.apiKeyIds - Array of API key IDs to remove
312
+ * @returns A promise that resolves to an array of results from each removal operation
313
+ */
314
+ this.deleteUserAuth = async (params) => {
315
+ try {
316
+ const { userId, phoneNumber, email, authenticatorIds, oauthProviderIds, apiKeyIds, } = params;
317
+ const promises = [];
318
+ if (phoneNumber) {
319
+ promises.push(this.updateUser({ userId, userPhoneNumber: "", userTagIds: [] }));
320
+ }
321
+ if (email) {
322
+ promises.push(this.updateUser({ userId, userEmail: "", userTagIds: [] }));
323
+ }
324
+ if (authenticatorIds && authenticatorIds.length > 0) {
325
+ promises.push(this.deleteAuthenticators({ userId, authenticatorIds }));
326
+ }
327
+ if (oauthProviderIds && oauthProviderIds.length > 0) {
328
+ promises.push(this.deleteOauthProviders({ userId, providerIds: oauthProviderIds }));
329
+ }
330
+ if (apiKeyIds && apiKeyIds.length > 0) {
331
+ promises.push(this.deleteApiKeys({ userId, apiKeyIds }));
332
+ }
333
+ // Execute all removal operations in parallel
334
+ return await Promise.all(promises);
335
+ }
336
+ catch (error) {
337
+ // Surface error
338
+ throw error;
339
+ }
340
+ };
341
+ /**
342
+ * Adds or updates authentication factors for an end user.
343
+ *
344
+ * This function allows selectively adding:
345
+ * - Phone number
346
+ * - Email
347
+ * - Authenticators
348
+ * - OAuth providers
349
+ * - API keys
350
+ *
351
+ * All additions/updates are executed in parallel if multiple
352
+ * parameters are provided.
353
+ *
354
+ * @param params - A structured object containing all the addition/update parameters
355
+ * @param params.userId - Unique identifier of the user
356
+ * @param params.phoneNumber - New phone number for the user
357
+ * @param params.email - New email address for the user
358
+ * @param params.authenticators - Array of authenticator objects to create
359
+ * @param params.oauthProviders - Array of OAuth provider objects to create
360
+ * @param params.apiKeys - Array of API key objects to create
361
+ * @returns A promise that resolves to an array of results from each addition or update
362
+ */
363
+ this.addUserAuth = async (params) => {
364
+ try {
365
+ const { userId, phoneNumber, email, authenticators, oauthProviders, apiKeys, } = params;
366
+ const promises = [];
367
+ if (phoneNumber) {
368
+ promises.push(this.updateUser({
369
+ userId,
370
+ userPhoneNumber: phoneNumber,
371
+ userTagIds: [],
372
+ }));
373
+ }
374
+ if (email) {
375
+ promises.push(this.updateUser({ userId, userEmail: email, userTagIds: [] }));
376
+ }
377
+ if (authenticators && authenticators.length > 0) {
378
+ promises.push(this.createAuthenticators({ userId, authenticators }));
379
+ }
380
+ if (oauthProviders && oauthProviders.length > 0) {
381
+ promises.push(this.createOauthProviders({ userId, oauthProviders }));
382
+ }
383
+ if (apiKeys && apiKeys.length > 0) {
384
+ promises.push(this.createApiKeys({ userId, apiKeys }));
385
+ }
386
+ // Execute all additions/updates operations in parallel
387
+ return await Promise.all(promises);
388
+ }
389
+ catch (error) {
390
+ // Surface error
391
+ throw error;
392
+ }
393
+ };
394
+ }
395
+ /**
396
+ * Comprehensive authentication update for an end user.
397
+ * Combines add/update and delete operations into a single call.
398
+ *
399
+ * The behavior is driven by whether values are set to:
400
+ * - A string/array (to create or update)
401
+ * - `null` or an array of IDs (to remove)
402
+ *
403
+ * All operations are executed in parallel where applicable.
404
+ *
405
+ * @param params - A structured object containing all the update parameters
406
+ * @param params.userId - Unique identifier of the user
407
+ * @param params.phoneNumber - String to set (new phone) or `null` to remove
408
+ * @param params.email - String to set (new email) or `null` to remove
409
+ * @param params.authenticators - Object describing authenticators to add or remove
410
+ * @param params.oauthProviders - Object describing OAuth providers to add or remove
411
+ * @param params.apiKeys - Object describing API keys to add or remove
412
+ *
413
+ * @returns A promise that resolves to a boolean indicating overall success
414
+ */
415
+ async updateUserAuth(params) {
416
+ try {
417
+ const { userId, phoneNumber, email, authenticators, oauthProviders, apiKeys, } = params;
418
+ const promises = [];
419
+ // Handle phone/email in a single updateUser call if both are changing,
420
+ // or separate calls if only one is changing.
421
+ const userUpdates = {};
422
+ if (phoneNumber !== undefined) {
423
+ userUpdates.userPhoneNumber = phoneNumber === null ? "" : phoneNumber;
424
+ }
425
+ if (email !== undefined) {
426
+ userUpdates.userEmail = email === null ? "" : email;
427
+ }
428
+ if (Object.keys(userUpdates).length > 0) {
429
+ promises.push(this.updateUser({ userId, ...userUpdates, userTagIds: [] }));
430
+ }
431
+ // Handle authenticators
432
+ if (authenticators) {
433
+ if (authenticators.add?.length) {
434
+ promises.push(this.createAuthenticators({
435
+ userId,
436
+ authenticators: authenticators.add,
437
+ }));
438
+ }
439
+ if (authenticators.deleteIds?.length) {
440
+ promises.push(this.deleteAuthenticators({
441
+ userId,
442
+ authenticatorIds: authenticators.deleteIds,
443
+ }));
444
+ }
445
+ }
446
+ // Handle OAuth providers
447
+ if (oauthProviders) {
448
+ if (oauthProviders.add?.length) {
449
+ promises.push(this.createOauthProviders({
450
+ userId,
451
+ oauthProviders: oauthProviders.add,
452
+ }));
453
+ }
454
+ if (oauthProviders.deleteIds?.length) {
455
+ promises.push(this.deleteOauthProviders({
456
+ userId,
457
+ providerIds: oauthProviders.deleteIds,
458
+ }));
459
+ }
460
+ }
461
+ // Handle API keys
462
+ if (apiKeys) {
463
+ if (apiKeys.add?.length) {
464
+ promises.push(this.createApiKeys({
465
+ userId,
466
+ apiKeys: apiKeys.add,
467
+ }));
468
+ }
469
+ if (apiKeys.deleteIds?.length) {
470
+ promises.push(this.deleteApiKeys({
471
+ userId,
472
+ apiKeyIds: apiKeys.deleteIds,
473
+ }));
474
+ }
475
+ }
476
+ // Execute all requested operations in parallel
477
+ await Promise.all(promises);
478
+ return true;
479
+ }
480
+ catch (error) {
481
+ // Surface error
482
+ throw error;
483
+ }
484
+ }
485
+ }
486
+ class TurnkeyPasskeyClient extends TurnkeyBrowserClient {
487
+ constructor(config) {
488
+ super(config, AuthClient.Passkey);
489
+ /**
490
+ * Create a passkey for an end-user, taking care of various lower-level details.
491
+ *
492
+ * @returns {Promise<Passkey>}
493
+ */
494
+ this.createUserPasskey = async (config = {}) => {
495
+ const challenge = generateRandomBuffer();
496
+ const encodedChallenge = base64UrlEncode(challenge);
497
+ const authenticatorUserId = generateRandomBuffer();
498
+ // WebAuthn credential options options can be found here:
499
+ // https://www.w3.org/TR/webauthn-2/#sctn-sample-registration
500
+ //
501
+ // All pubkey algorithms can be found here: https://www.iana.org/assignments/cose/cose.xhtml#algorithms
502
+ // Turnkey only supports ES256 (-7) and RS256 (-257)
503
+ //
504
+ // The pubkey type only supports one value, "public-key"
505
+ // See https://www.w3.org/TR/webauthn-2/#enumdef-publickeycredentialtype for more details
506
+ // TODO: consider un-nesting these config params
507
+ const webauthnConfig = {
508
+ publicKey: {
509
+ rp: {
510
+ id: config.publicKey?.rp?.id ?? this.rpId,
511
+ name: config.publicKey?.rp?.name ?? "",
512
+ },
513
+ challenge: config.publicKey?.challenge ?? challenge,
514
+ pubKeyCredParams: config.publicKey?.pubKeyCredParams ?? [
515
+ {
516
+ type: "public-key",
517
+ alg: -7,
518
+ },
519
+ {
520
+ type: "public-key",
521
+ alg: -257,
522
+ },
523
+ ],
524
+ user: {
525
+ id: config.publicKey?.user?.id ?? authenticatorUserId,
526
+ name: config.publicKey?.user?.name ?? "Default User",
527
+ displayName: config.publicKey?.user?.displayName ?? "Default User",
528
+ },
529
+ authenticatorSelection: {
530
+ authenticatorAttachment: config.publicKey?.authenticatorSelection?.authenticatorAttachment ??
531
+ undefined,
532
+ requireResidentKey: config.publicKey?.authenticatorSelection?.requireResidentKey ??
533
+ true,
534
+ residentKey: config.publicKey?.authenticatorSelection?.residentKey ?? "required",
535
+ userVerification: config.publicKey?.authenticatorSelection?.userVerification ??
536
+ "preferred",
537
+ },
538
+ },
539
+ };
540
+ const attestation = await getWebAuthnAttestation(webauthnConfig);
541
+ return {
542
+ encodedChallenge: config.publicKey?.challenge
543
+ ? base64UrlEncode(config.publicKey?.challenge)
544
+ : encodedChallenge,
545
+ attestation,
546
+ };
547
+ };
548
+ /**
549
+ * Uses passkey authentication to create a read-write session, via an embedded API key,
550
+ * and stores + returns the resulting auth bundle that contains the encrypted API key.
551
+ * This auth bundle (also referred to as a credential bundle) can be injected into an `iframeStamper`,
552
+ * resulting in a touch-free authenticator. Unlike `loginWithReadWriteSession`, this method
553
+ * assumes the end-user's organization ID (i.e. the sub-organization ID) is already known.
554
+ *
555
+ * @param userId
556
+ * @param targetEmbeddedKey
557
+ * @param expirationSeconds
558
+ * @param curveType
559
+ * @returns {Promise<ReadWriteSession>}
560
+ */
561
+ this.createPasskeySession = async (userId, targetEmbeddedKey, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, organizationId) => {
562
+ try {
563
+ const session = await getStorageValue(StorageKeys.Session);
564
+ organizationId = organizationId ?? session?.organizationId;
565
+ userId = userId ?? session?.userId;
566
+ if (!organizationId) {
567
+ throw new Error("Error creating passkey session: Organization ID is required");
568
+ }
569
+ if (!userId) {
570
+ throw new Error("Error creating passkey session: User ID is required");
571
+ }
572
+ const { authBundle: credentialBundle, publicKey } = await createEmbeddedAPIKey(targetEmbeddedKey);
573
+ // add API key to Turnkey User
574
+ await this.createApiKeys({
575
+ organizationId,
576
+ userId,
577
+ apiKeys: [
578
+ {
579
+ apiKeyName: `Session Key ${String(Date.now())}`,
580
+ publicKey,
581
+ expirationSeconds,
582
+ curveType: "API_KEY_CURVE_P256",
583
+ },
584
+ ],
585
+ });
586
+ const whoAmI = await this.getWhoami();
587
+ const expiry = Date.now() + Number(expirationSeconds) * 1000;
588
+ await saveSession({
589
+ organizationId,
590
+ organizationName: whoAmI?.organizationName ?? "",
591
+ userId,
592
+ username: whoAmI?.username ?? "",
593
+ credentialBundle,
594
+ sessionExpiry: expiry,
595
+ }, this.authClient);
596
+ return {
597
+ credentialBundle,
598
+ expiry,
599
+ };
600
+ }
601
+ catch (error) {
602
+ throw new Error("Unable to create passkey session.");
603
+ }
604
+ };
605
+ this.rpId = this.stamper.rpId;
606
+ }
607
+ }
608
+ /**
609
+ * TurnkeyIframeClient is a client that uses an iframe to interact with the Turnkey API.
610
+ * It is used to create read-write sessions, and to inject credential bundles into the iframe.
611
+ * It is also used to extract encrypted credential bundles from the iframe.
612
+ * @extends TurnkeyBrowserClient
613
+ */
614
+ class TurnkeyIframeClient extends TurnkeyBrowserClient {
615
+ constructor(config) {
616
+ super(config, AuthClient.Iframe);
617
+ this.injectCredentialBundle = async (credentialBundle) => {
618
+ return await this.stamper.injectCredentialBundle(credentialBundle);
619
+ };
620
+ this.injectWalletExportBundle = async (credentialBundle, organizationId) => {
621
+ return await this.stamper.injectWalletExportBundle(credentialBundle, organizationId);
622
+ };
623
+ this.injectKeyExportBundle = async (credentialBundle, organizationId, keyFormat) => {
624
+ return await this.stamper.injectKeyExportBundle(credentialBundle, organizationId, keyFormat);
625
+ };
626
+ this.injectImportBundle = async (bundle, organizationId, userId) => {
627
+ return await this.stamper.injectImportBundle(bundle, organizationId, userId);
628
+ };
629
+ this.extractWalletEncryptedBundle = async () => {
630
+ return await this.stamper.extractWalletEncryptedBundle();
631
+ };
632
+ this.extractKeyEncryptedBundle = async () => {
633
+ return await this.stamper.extractKeyEncryptedBundle();
634
+ };
635
+ this.iframePublicKey = this.stamper.iframePublicKey;
636
+ }
637
+ }
638
+ class TurnkeyWalletClient extends TurnkeyBrowserClient {
639
+ constructor(config) {
640
+ super(config, AuthClient.Wallet);
641
+ this.wallet = config.wallet;
642
+ }
643
+ async getPublicKey() {
644
+ return this.wallet.getPublicKey();
645
+ }
646
+ getWalletInterface() {
647
+ return this.wallet;
648
+ }
649
+ }
650
+
651
+ export { TurnkeyBrowserClient, TurnkeyIframeClient, TurnkeyPasskeyClient, TurnkeyWalletClient };
652
+ //# sourceMappingURL=browser-clients.mjs.map