@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.
- package/dist/react-native/ParaMobile.d.ts +1 -0
- package/dist/react-native/ParaMobile.js +107 -71
- package/dist/react-native/ReactNativeUtils.d.ts +1 -0
- package/dist/react-native/ReactNativeUtils.js +3 -0
- package/package.json +7 -7
- package/src/react-native/ParaMobile.ts +26 -0
- package/src/react-native/ReactNativeUtils.ts +10 -0
|
@@ -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
|
-
|
|
206
|
+
yield __classPrivateFieldGet(this, _ParaMobile_instances, "m", _ParaMobile_runLoginWithPasskey).call(this);
|
|
210
207
|
}
|
|
211
208
|
catch (error) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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.
|
|
4
|
+
"version": "2.28.0",
|
|
5
5
|
"author": "Para Team <hello@getpara.com> (https://getpara.com)",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@getpara/core-sdk": "2.
|
|
8
|
-
"@getpara/react-core": "2.
|
|
9
|
-
"@getpara/user-management-client": "2.
|
|
10
|
-
"@getpara/viem-v2-integration": "2.
|
|
11
|
-
"@getpara/web-sdk": "2.
|
|
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": "
|
|
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);
|