@getpara/react-native-wallet 2.26.0 → 2.28.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.
@@ -7,6 +7,7 @@ import { AuthStateSignup, ConstructorOpts, ParaCore, Environment, PlatformUtils,
7
7
  * const para = new ParaMobile(Environment.BETA, "api_key");
8
8
  */
9
9
  export declare class ParaMobile extends ParaCore {
10
+ #private;
10
11
  isNativePasskey: boolean;
11
12
  private relyingPartyId;
12
13
  /**
@@ -7,6 +7,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
11
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
12
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
13
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
14
+ };
15
+ var _ParaMobile_instances, _ParaMobile_runLoginWithPasskey;
10
16
  import { ParaCore, Environment, decryptPrivateKeyAndDecryptShare, encryptPrivateKey, getAsymmetricKeyPair, getDerivedPrivateKeyAndDecrypt, getPublicKeyHex, getSHA256HashHex, parseCredentialCreationRes, } from '@getpara/web-sdk';
11
17
  import { ReactNativeUtils } from './ReactNativeUtils.js';
12
18
  import { Passkey, } from 'react-native-passkey';
@@ -78,6 +84,7 @@ export class ParaMobile extends ParaCore {
78
84
  }
79
85
  env = ParaCore.resolveEnvironment(env, apiKey); // Ensure the environment is resolved before calling super
80
86
  super(env, apiKey, opts);
87
+ _ParaMobile_instances.add(this);
81
88
  this.isNativePasskey = true;
82
89
  setEnv(env);
83
90
  if (relyingPartyId) {
@@ -195,81 +202,31 @@ export class ParaMobile extends ParaCore {
195
202
  */
196
203
  loginWithPasskey() {
197
204
  return __awaiter(this, void 0, void 0, function* () {
198
- this.assertIsAuthSet();
199
- const userId = this.assertUserId();
200
- const { challenge, allowedPublicKeys } = yield this.ctx.client.getWebChallenge({ userId });
201
- const requestJson = {
202
- challenge,
203
- timeout: 60000,
204
- rpId: this.relyingPartyId,
205
- allowCredentials: (allowedPublicKeys === null || allowedPublicKeys === void 0 ? void 0 : allowedPublicKeys[0]) ? [{ type: 'public-key', id: allowedPublicKeys[0] }] : [],
206
- };
207
- let result;
208
205
  try {
209
- result = yield Passkey.get(requestJson);
206
+ yield __classPrivateFieldGet(this, _ParaMobile_instances, "m", _ParaMobile_runLoginWithPasskey).call(this);
210
207
  }
211
208
  catch (error) {
212
- const enriched = mapPasskeyError(error);
213
- logPasskeyDiagnostic(enriched, 'login', this.relyingPartyId);
214
- throw enriched;
215
- }
216
- let resultJson;
217
- if (typeof result === 'string') {
218
- resultJson = JSON.parse(result);
219
- }
220
- else {
221
- resultJson = result;
222
- }
223
- const { partnerId, sessionLookupId } = yield this.ctx.client.touchSession();
224
- const publicKey = resultJson.id;
225
- const verifyWebChallengeResult = yield this.ctx.client.verifyWebChallenge(partnerId, {
226
- publicKey,
227
- signature: {
228
- clientDataJSON: resultJson.response.clientDataJSON,
229
- authenticatorData: resultJson.response.authenticatorData,
230
- signature: resultJson.response.signature,
231
- },
232
- });
233
- if (userId !== verifyWebChallengeResult.data.userId) {
234
- throw new Error('User ID mismatch');
235
- }
236
- const encryptedSharesResult = yield this.ctx.client.getBiometricKeyshares(userId, resultJson.id);
237
- const encryptionKeyHash = getSHA256HashHex(resultJson.response.userHandle);
238
- const { encryptedPrivateKeys } = yield this.ctx.client.getEncryptedWalletPrivateKeys(userId, encryptionKeyHash);
239
- let decryptedShares;
240
- if (encryptedPrivateKeys.length === 0) {
241
- decryptedShares = yield getDerivedPrivateKeyAndDecrypt(this.ctx, resultJson.response.userHandle, encryptedSharesResult.data.keyShares);
242
- const keyPair = yield getAsymmetricKeyPair(this.ctx, resultJson.response.userHandle);
243
- const encryptedPrivateKeyHex = yield encryptPrivateKey(keyPair, resultJson.response.userHandle);
244
- yield this.ctx.client.uploadEncryptedWalletPrivateKey(userId, encryptedPrivateKeyHex, encryptionKeyHash, resultJson.id);
245
- }
246
- else {
247
- decryptedShares = yield decryptPrivateKeyAndDecryptShare(resultJson.response.userHandle, encryptedSharesResult.data.keyShares, encryptedPrivateKeys[0].encryptedPrivateKey);
209
+ // The native passkey ceremony runs on a different async chain from
210
+ // `authenticateWithOAuth` / `authenticateWithEmailOrPhone`, which put the
211
+ // auth state machine into `waiting_for_session` and poll until the session
212
+ // flips to authenticated. If the passkey ceremony or any subsequent step
213
+ // throws (no credential available, user cancelled, misconfigured RP,
214
+ // user-id mismatch, network error, etc.), nothing else cancels that
215
+ // polling — the caller awaiting `authenticateWithOAuth` would otherwise
216
+ // hang forever.
217
+ //
218
+ // Cancel the auth state machine here so the parent flow rejects with a
219
+ // clear "Authentication canceled" error instead of hanging. `cancelAuthFlow`
220
+ // no-ops when the state machine is already unauthenticated/error, so this
221
+ // is safe when `loginWithPasskey` is called outside of an auth flow.
222
+ try {
223
+ yield this.cancelAuthFlow();
224
+ }
225
+ catch (_a) {
226
+ // Best-effort. Surface the original passkey error regardless.
227
+ }
228
+ throw error;
248
229
  }
249
- const walletsRes = yield this.ctx.client.getWallets(userId);
250
- const desiredWallets = walletsRes.data.wallets;
251
- const walletsToInsert = {};
252
- for (let desiredWallet of desiredWallets) {
253
- const decryptedShare = decryptedShares.find(share => share.walletId === desiredWallet.id);
254
- walletsToInsert[decryptedShare.walletId] = {
255
- id: decryptedShare.walletId,
256
- signer: decryptedShare.signer,
257
- address: desiredWallet.address || undefined,
258
- publicKey: desiredWallet.publicKey || undefined,
259
- scheme: desiredWallet.scheme,
260
- type: desiredWallet.type || undefined,
261
- };
262
- }
263
- const currentWalletIds = {};
264
- for (const wallet of Object.values(walletsToInsert)) {
265
- const { id, type } = wallet;
266
- const currentIdsForType = currentWalletIds[type || 'EVM'] || [];
267
- currentWalletIds[type || 'EVM'] = [...currentIdsForType, id];
268
- }
269
- yield this.setWallets(walletsToInsert);
270
- yield this.setCurrentWalletIds(currentWalletIds, {
271
- sessionLookupId,
272
- });
273
230
  });
274
231
  }
275
232
  /**
@@ -307,3 +264,82 @@ export class ParaMobile extends ParaCore {
307
264
  (_b = (_a = this.platformUtils).postMessage) === null || _b === void 0 ? void 0 : _b.call(_a, { data });
308
265
  }
309
266
  }
267
+ _ParaMobile_instances = new WeakSet(), _ParaMobile_runLoginWithPasskey = function _ParaMobile_runLoginWithPasskey() {
268
+ return __awaiter(this, void 0, void 0, function* () {
269
+ this.assertIsAuthSet();
270
+ const userId = this.assertUserId();
271
+ const { challenge, allowedPublicKeys } = yield this.ctx.client.getWebChallenge({ userId });
272
+ const requestJson = {
273
+ challenge,
274
+ timeout: 60000,
275
+ rpId: this.relyingPartyId,
276
+ allowCredentials: (allowedPublicKeys === null || allowedPublicKeys === void 0 ? void 0 : allowedPublicKeys[0]) ? [{ type: 'public-key', id: allowedPublicKeys[0] }] : [],
277
+ };
278
+ let result;
279
+ try {
280
+ result = yield Passkey.get(requestJson);
281
+ }
282
+ catch (error) {
283
+ const enriched = mapPasskeyError(error);
284
+ logPasskeyDiagnostic(enriched, 'login', this.relyingPartyId);
285
+ throw enriched;
286
+ }
287
+ let resultJson;
288
+ if (typeof result === 'string') {
289
+ resultJson = JSON.parse(result);
290
+ }
291
+ else {
292
+ resultJson = result;
293
+ }
294
+ const { partnerId, sessionLookupId } = yield this.ctx.client.touchSession();
295
+ const publicKey = resultJson.id;
296
+ const verifyWebChallengeResult = yield this.ctx.client.verifyWebChallenge(partnerId, {
297
+ publicKey,
298
+ signature: {
299
+ clientDataJSON: resultJson.response.clientDataJSON,
300
+ authenticatorData: resultJson.response.authenticatorData,
301
+ signature: resultJson.response.signature,
302
+ },
303
+ });
304
+ if (userId !== verifyWebChallengeResult.data.userId) {
305
+ throw new Error('User ID mismatch');
306
+ }
307
+ const encryptedSharesResult = yield this.ctx.client.getBiometricKeyshares(userId, resultJson.id);
308
+ const encryptionKeyHash = getSHA256HashHex(resultJson.response.userHandle);
309
+ const { encryptedPrivateKeys } = yield this.ctx.client.getEncryptedWalletPrivateKeys(userId, encryptionKeyHash);
310
+ let decryptedShares;
311
+ if (encryptedPrivateKeys.length === 0) {
312
+ decryptedShares = yield getDerivedPrivateKeyAndDecrypt(this.ctx, resultJson.response.userHandle, encryptedSharesResult.data.keyShares);
313
+ const keyPair = yield getAsymmetricKeyPair(this.ctx, resultJson.response.userHandle);
314
+ const encryptedPrivateKeyHex = yield encryptPrivateKey(keyPair, resultJson.response.userHandle);
315
+ yield this.ctx.client.uploadEncryptedWalletPrivateKey(userId, encryptedPrivateKeyHex, encryptionKeyHash, resultJson.id);
316
+ }
317
+ else {
318
+ decryptedShares = yield decryptPrivateKeyAndDecryptShare(resultJson.response.userHandle, encryptedSharesResult.data.keyShares, encryptedPrivateKeys[0].encryptedPrivateKey);
319
+ }
320
+ const walletsRes = yield this.ctx.client.getWallets(userId);
321
+ const desiredWallets = walletsRes.data.wallets;
322
+ const walletsToInsert = {};
323
+ for (let desiredWallet of desiredWallets) {
324
+ const decryptedShare = decryptedShares.find(share => share.walletId === desiredWallet.id);
325
+ walletsToInsert[decryptedShare.walletId] = {
326
+ id: decryptedShare.walletId,
327
+ signer: decryptedShare.signer,
328
+ address: desiredWallet.address || undefined,
329
+ publicKey: desiredWallet.publicKey || undefined,
330
+ scheme: desiredWallet.scheme,
331
+ type: desiredWallet.type || undefined,
332
+ };
333
+ }
334
+ const currentWalletIds = {};
335
+ for (const wallet of Object.values(walletsToInsert)) {
336
+ const { id, type } = wallet;
337
+ const currentIdsForType = currentWalletIds[type || 'EVM'] || [];
338
+ currentWalletIds[type || 'EVM'] = [...currentIdsForType, id];
339
+ }
340
+ yield this.setWallets(walletsToInsert);
341
+ yield this.setCurrentWalletIds(currentWalletIds, {
342
+ sessionLookupId,
343
+ });
344
+ });
345
+ };
@@ -44,6 +44,7 @@ export declare class ReactNativeUtils implements PlatformUtils {
44
44
  walletId: string;
45
45
  }>;
46
46
  getPrivateKey(_ctx: Ctx, _userId: string, _walletId: string, _share: string, _sessionCookie: string): Promise<string>;
47
+ getED25519PrivateKey(_ctx: Ctx, _userId: string, _walletId: string, _share: string, _sessionCookie: string): Promise<string>;
47
48
  openPopup(popupUrl: string): any;
48
49
  private baseSignTransaction;
49
50
  sendTransaction(ctx: Ctx, userId: string, walletId: string, share: string, rlpEncodedTxBase64: string, chainId: string, _sessionCookie: string, isDKLS?: boolean): Promise<SignatureRes>;
@@ -118,6 +118,9 @@ export class ReactNativeUtils {
118
118
  getPrivateKey(_ctx, _userId, _walletId, _share, _sessionCookie) {
119
119
  throw new Error('Method not implemented.');
120
120
  }
121
+ getED25519PrivateKey(_ctx, _userId, _walletId, _share, _sessionCookie) {
122
+ throw new Error('Method not implemented.');
123
+ }
121
124
  openPopup(popupUrl) {
122
125
  if (this.transactionReviewHandler) {
123
126
  this.transactionReviewHandler(popupUrl);
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@getpara/react-native-wallet",
3
3
  "description": "Para Wallet for React Native",
4
- "version": "2.26.0",
4
+ "version": "2.28.0",
5
5
  "author": "Para Team <hello@getpara.com> (https://getpara.com)",
6
6
  "dependencies": {
7
- "@getpara/core-sdk": "2.26.0",
8
- "@getpara/react-core": "2.26.0",
9
- "@getpara/user-management-client": "2.26.0",
10
- "@getpara/viem-v2-integration": "2.26.0",
11
- "@getpara/web-sdk": "2.26.0",
7
+ "@getpara/core-sdk": "2.28.0",
8
+ "@getpara/react-core": "2.28.0",
9
+ "@getpara/user-management-client": "2.28.0",
10
+ "@getpara/viem-v2-integration": "2.28.0",
11
+ "@getpara/web-sdk": "2.28.0",
12
12
  "@peculiar/webcrypto": "^1.5.0",
13
13
  "@ungap/structured-clone": "1.3.0",
14
14
  "react-native-url-polyfill": "2.0.0",
@@ -222,5 +222,5 @@
222
222
  ]
223
223
  }
224
224
  },
225
- "gitHead": "02eb43f55b1c5f8bed6685940b6344551610f42a"
225
+ "gitHead": "cb74d2b02aed6a8a5373249418b315d686b83b66"
226
226
  }
@@ -239,6 +239,32 @@ export class ParaMobile extends ParaCore {
239
239
  * @returns {Promise<void>}
240
240
  */
241
241
  async loginWithPasskey(): Promise<void> {
242
+ try {
243
+ await this.#runLoginWithPasskey();
244
+ } catch (error) {
245
+ // The native passkey ceremony runs on a different async chain from
246
+ // `authenticateWithOAuth` / `authenticateWithEmailOrPhone`, which put the
247
+ // auth state machine into `waiting_for_session` and poll until the session
248
+ // flips to authenticated. If the passkey ceremony or any subsequent step
249
+ // throws (no credential available, user cancelled, misconfigured RP,
250
+ // user-id mismatch, network error, etc.), nothing else cancels that
251
+ // polling — the caller awaiting `authenticateWithOAuth` would otherwise
252
+ // hang forever.
253
+ //
254
+ // Cancel the auth state machine here so the parent flow rejects with a
255
+ // clear "Authentication canceled" error instead of hanging. `cancelAuthFlow`
256
+ // no-ops when the state machine is already unauthenticated/error, so this
257
+ // is safe when `loginWithPasskey` is called outside of an auth flow.
258
+ try {
259
+ await this.cancelAuthFlow();
260
+ } catch {
261
+ // Best-effort. Surface the original passkey error regardless.
262
+ }
263
+ throw error;
264
+ }
265
+ }
266
+
267
+ async #runLoginWithPasskey(): Promise<void> {
242
268
  this.assertIsAuthSet();
243
269
  const userId = this.assertUserId();
244
270
 
@@ -158,6 +158,16 @@ export class ReactNativeUtils implements PlatformUtils {
158
158
  throw new Error('Method not implemented.');
159
159
  }
160
160
 
161
+ getED25519PrivateKey(
162
+ _ctx: Ctx,
163
+ _userId: string,
164
+ _walletId: string,
165
+ _share: string,
166
+ _sessionCookie: string,
167
+ ): Promise<string> {
168
+ throw new Error('Method not implemented.');
169
+ }
170
+
161
171
  openPopup(popupUrl: string): any {
162
172
  if (this.transactionReviewHandler) {
163
173
  this.transactionReviewHandler(popupUrl);