@getpara/react-native-wallet 1.7.1 → 2.0.0-alpha.3

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.
@@ -1,7 +1,10 @@
1
1
  import { StorageUtils } from '@getpara/web-sdk';
2
+ /**
3
+ * Implements `StorageUtils` using React Native Async Storage.
4
+ */
2
5
  export declare class AsyncStorage implements StorageUtils {
6
+ clear(prefix: string): Promise<void>;
3
7
  get(key: string): Promise<string | null>;
4
- set(key: string, value: string): Promise<void>;
5
8
  removeItem(key: string): Promise<void>;
6
- clear(prefix: string): Promise<void>;
9
+ set(key: string, value: string): Promise<void>;
7
10
  }
@@ -8,56 +8,33 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import RNAsyncStorage from '@react-native-async-storage/async-storage';
11
+ /**
12
+ * Implements `StorageUtils` using React Native Async Storage.
13
+ */
11
14
  export class AsyncStorage {
12
- get(key) {
15
+ clear(prefix) {
13
16
  return __awaiter(this, void 0, void 0, function* () {
14
- try {
15
- return yield RNAsyncStorage.getItem(key);
16
- }
17
- catch (error) {
18
- console.warn('Error retrieving stored item:', error);
19
- return null;
17
+ const keys = yield RNAsyncStorage.getAllKeys();
18
+ for (const key of keys) {
19
+ if (key.startsWith(prefix)) {
20
+ yield RNAsyncStorage.removeItem(key);
21
+ }
20
22
  }
21
23
  });
22
24
  }
23
- set(key, value) {
25
+ get(key) {
24
26
  return __awaiter(this, void 0, void 0, function* () {
25
- try {
26
- yield RNAsyncStorage.setItem(key, value);
27
- }
28
- catch (error) {
29
- console.warn(`Error storing key ${key}:`, error);
30
- }
27
+ return RNAsyncStorage.getItem(key);
31
28
  });
32
29
  }
33
30
  removeItem(key) {
34
31
  return __awaiter(this, void 0, void 0, function* () {
35
- try {
36
- yield RNAsyncStorage.removeItem(key);
37
- }
38
- catch (error) {
39
- console.warn(`Error removing key ${key}:`, error);
40
- }
32
+ yield RNAsyncStorage.removeItem(key);
41
33
  });
42
34
  }
43
- clear(prefix) {
35
+ set(key, value) {
44
36
  return __awaiter(this, void 0, void 0, function* () {
45
- try {
46
- const keys = yield RNAsyncStorage.getAllKeys();
47
- for (const key of keys) {
48
- if (key.startsWith(prefix)) {
49
- try {
50
- yield RNAsyncStorage.removeItem(key);
51
- }
52
- catch (error) {
53
- console.warn(`Error clearing key ${key}:`, error);
54
- }
55
- }
56
- }
57
- }
58
- catch (error) {
59
- console.warn(`Error getting keys for prefix ${prefix}:`, error);
60
- }
37
+ yield RNAsyncStorage.setItem(key, value);
61
38
  });
62
39
  }
63
40
  }
@@ -1,4 +1,7 @@
1
1
  import { StorageUtils } from '@getpara/web-sdk';
2
+ /**
3
+ * Implements `StorageUtils` using React Native `Keychain`.
4
+ */
2
5
  export declare class KeychainStorage implements StorageUtils {
3
6
  get(key: string): Promise<string | null>;
4
7
  set(key: string, value: string): Promise<void>;
@@ -9,6 +9,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import Keychain from 'react-native-keychain';
11
11
  const USERNAME = '@CAPSULE';
12
+ const KEYCHAIN_USER_CANCELLED_ERRORS = [
13
+ 'user canceled the operation',
14
+ 'error: code: 13, msg: cancel',
15
+ 'error: code: 10, msg: fingerprint operation canceled by the user',
16
+ ];
17
+ function isUserCancelledError(error) {
18
+ return KEYCHAIN_USER_CANCELLED_ERRORS.some(userCancelledError => error.toString().toLowerCase().includes(userCancelledError));
19
+ }
20
+ /**
21
+ * Implements `StorageUtils` using React Native `Keychain`.
22
+ */
12
23
  export class KeychainStorage {
13
24
  get(key) {
14
25
  return __awaiter(this, void 0, void 0, function* () {
@@ -22,57 +33,39 @@ export class KeychainStorage {
22
33
  return item.password;
23
34
  }
24
35
  catch (error) {
25
- console.warn('Error retrieving stored item:', error);
26
- return null;
36
+ if (error instanceof Error && !isUserCancelledError(error)) {
37
+ // triggered when biometry verification fails and user cancels the action
38
+ throw new Error('Error retrieving stored item ' + error.message);
39
+ }
40
+ throw error;
27
41
  }
28
42
  });
29
43
  }
30
44
  set(key, value) {
31
45
  return __awaiter(this, void 0, void 0, function* () {
32
- try {
33
- const result = yield Keychain.setGenericPassword(USERNAME, value, {
34
- service: key,
35
- accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
36
- securityLevel: Keychain.SECURITY_LEVEL.ANY,
37
- storage: Keychain.STORAGE_TYPE.AES_GCM_NO_AUTH,
38
- });
39
- if (!result) {
40
- console.warn(`Failed to store key ${key}`);
41
- }
42
- }
43
- catch (error) {
44
- console.warn(`Error storing key ${key}:`, error);
46
+ const result = yield Keychain.setGenericPassword(USERNAME, value, {
47
+ service: key,
48
+ accessible: Keychain.ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
49
+ securityLevel: Keychain.SECURITY_LEVEL.ANY,
50
+ });
51
+ if (!result) {
52
+ throw new Error('Failed to store key ' + key);
45
53
  }
46
54
  });
47
55
  }
48
56
  removeItem(key) {
49
57
  return __awaiter(this, void 0, void 0, function* () {
50
- try {
51
- yield Keychain.resetGenericPassword({ service: key });
52
- }
53
- catch (error) {
54
- console.warn(`Error removing key ${key}:`, error);
55
- }
58
+ yield Keychain.resetGenericPassword({ service: key });
56
59
  });
57
60
  }
58
61
  clear(prefix) {
59
62
  return __awaiter(this, void 0, void 0, function* () {
60
- try {
61
- const services = yield Keychain.getAllGenericPasswordServices();
62
- for (const key of services) {
63
- if (key && key.startsWith(prefix)) {
64
- try {
65
- yield Keychain.resetGenericPassword({ service: key });
66
- }
67
- catch (error) {
68
- console.warn(`Error clearing key ${key}:`, error);
69
- }
70
- }
63
+ const services = yield Keychain.getAllGenericPasswordServices();
64
+ for (const key of services) {
65
+ if (key && key.startsWith(prefix)) {
66
+ yield Keychain.resetGenericPassword({ service: key });
71
67
  }
72
68
  }
73
- catch (error) {
74
- console.warn(`Error getting services for prefix ${prefix}:`, error);
75
- }
76
69
  });
77
70
  }
78
71
  }
@@ -1,5 +1,4 @@
1
- import { ConstructorOpts, ParaCore, Environment, PlatformUtils } from '@getpara/web-sdk';
2
- import { Auth } from '@getpara/user-management-client';
1
+ import { AuthStateSignup, ConstructorOpts, ParaCore, Environment, PlatformUtils } from '@getpara/web-sdk';
3
2
  /**
4
3
  * Represents a mobile implementation of the Para SDK.
5
4
  * @extends ParaCore
@@ -8,6 +7,7 @@ import { Auth } from '@getpara/user-management-client';
8
7
  * const para = new ParaMobile(Environment.BETA, "api_key");
9
8
  */
10
9
  export declare class ParaMobile extends ParaCore {
10
+ isNativePasskey: boolean;
11
11
  private relyingPartyId;
12
12
  /**
13
13
  * Creates an instance of ParaMobile.
@@ -16,37 +16,19 @@ export declare class ParaMobile extends ParaCore {
16
16
  * @param {string} [relyingPartyId] - The relying party ID for WebAuthn.
17
17
  * @param {ConstructorOpts} [opts] - Additional constructor options.
18
18
  */
19
- constructor(env: Environment, apiKey?: string, relyingPartyId?: string, opts?: ConstructorOpts);
19
+ constructor(env: Environment, apiKey: string, relyingPartyId?: string, opts?: ConstructorOpts);
20
20
  protected getPlatformUtils(): PlatformUtils;
21
- /**
22
- * Verifies an email and returns the biometrics ID.
23
- * @param {string} verificationCode - The verification code sent to the email.
24
- * @returns {Promise<string>} The biometrics ID.
25
- */
26
- verifyEmailBiometricsId({ verificationCode }: {
27
- verificationCode: string;
28
- }): Promise<string>;
29
- /**
30
- * Verifies a phone number and returns the biometrics ID.
31
- * @param {string} verificationCode - The verification code sent to the phone.
32
- * @returns {Promise<string>} The biometrics ID.
33
- */
34
- verifyPhoneBiometricsId({ verificationCode }: {
35
- verificationCode: string;
36
- }): Promise<string>;
37
21
  /**
38
22
  * Registers a passkey for the user.
39
23
  * @param {Auth<'email'> | Auth<'phone'>} auth - The user's authentication details
40
24
  * @param {string} biometricsId - The biometrics ID obtained from verification.
41
25
  * @returns {Promise<void>}
42
26
  */
43
- registerPasskey({ biometricsId, ...auth }: {
44
- biometricsId: string;
45
- } & (Auth<'email'> | Auth<'phone'>)): Promise<void>;
27
+ registerPasskey(authState: AuthStateSignup): Promise<void>;
46
28
  /**
47
29
  * Logs in the user using their authentication credentials.
48
30
  * @param {AuthParams} params - The authentication parameters.
49
31
  * @returns {Promise<void>}
50
32
  */
51
- login({ ...auth }: Auth<'email'> | Auth<'phone'>): Promise<void>;
33
+ login(): Promise<void>;
52
34
  }
@@ -7,22 +7,11 @@ 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 __rest = (this && this.__rest) || function (s, e) {
11
- var t = {};
12
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
13
- t[p] = s[p];
14
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
15
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
16
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
17
- t[p[i]] = s[p[i]];
18
- }
19
- return t;
20
- };
21
10
  import { ParaCore, Environment, decryptPrivateKeyAndDecryptShare, encryptPrivateKey, getAsymmetricKeyPair, getDerivedPrivateKeyAndDecrypt, getPublicKeyHex, getSHA256HashHex, parseCredentialCreationRes, } from '@getpara/web-sdk';
22
11
  import * as Sentry from '@sentry/react-native';
23
12
  import { ReactNativeUtils } from './ReactNativeUtils.js';
24
13
  import { Passkey, } from 'react-native-passkey';
25
- import { extractAuthInfo, PublicKeyStatus } from '@getpara/user-management-client';
14
+ import { PublicKeyStatus } from '@getpara/user-management-client';
26
15
  import { setEnv } from '../config.js';
27
16
  import base64url from 'base64url';
28
17
  import { webcrypto } from 'crypto';
@@ -45,6 +34,7 @@ export class ParaMobile extends ParaCore {
45
34
  */
46
35
  constructor(env, apiKey, relyingPartyId, opts) {
47
36
  super(env, apiKey, opts);
37
+ this.isNativePasskey = true;
48
38
  // starting with non-prod to see what kind of errors we get and if sensitive data is tracked
49
39
  // will turn on in prod after monitoring
50
40
  if (env !== Environment.PROD && env !== Environment.DEV) {
@@ -76,56 +66,25 @@ export class ParaMobile extends ParaCore {
76
66
  getPlatformUtils() {
77
67
  return new ReactNativeUtils();
78
68
  }
79
- /**
80
- * Verifies an email and returns the biometrics ID.
81
- * @param {string} verificationCode - The verification code sent to the email.
82
- * @returns {Promise<string>} The biometrics ID.
83
- */
84
- verifyEmailBiometricsId(_a) {
85
- const _super = Object.create(null, {
86
- verifyEmail: { get: () => super.verifyEmail }
87
- });
88
- return __awaiter(this, arguments, void 0, function* ({ verificationCode }) {
89
- const webAuthCreateUrl = yield _super.verifyEmail.call(this, { verificationCode });
90
- const segments = webAuthCreateUrl.split('/');
91
- const segments2 = segments[segments.length - 1].split('?');
92
- const biometricsId = segments2[0];
93
- return biometricsId;
94
- });
95
- }
96
- /**
97
- * Verifies a phone number and returns the biometrics ID.
98
- * @param {string} verificationCode - The verification code sent to the phone.
99
- * @returns {Promise<string>} The biometrics ID.
100
- */
101
- verifyPhoneBiometricsId(_a) {
102
- const _super = Object.create(null, {
103
- verifyPhone: { get: () => super.verifyPhone }
104
- });
105
- return __awaiter(this, arguments, void 0, function* ({ verificationCode }) {
106
- const webAuthCreateUrl = yield _super.verifyPhone.call(this, { verificationCode });
107
- const segments = webAuthCreateUrl.split('/');
108
- const segments2 = segments[segments.length - 1].split('?');
109
- const biometricsId = segments2[0];
110
- return biometricsId;
111
- });
112
- }
113
69
  /**
114
70
  * Registers a passkey for the user.
115
71
  * @param {Auth<'email'> | Auth<'phone'>} auth - The user's authentication details
116
72
  * @param {string} biometricsId - The biometrics ID obtained from verification.
117
73
  * @returns {Promise<void>}
118
74
  */
119
- registerPasskey(_a) {
75
+ registerPasskey(authState) {
120
76
  return __awaiter(this, void 0, void 0, function* () {
121
- var { biometricsId } = _a, auth = __rest(_a, ["biometricsId"]);
77
+ if (!authState.passkeyId) {
78
+ throw new Error('Passkey ID not found. Make sure you have enabled passkey logins in the Para Developer Portal.');
79
+ }
80
+ const userId = this.assertUserId();
81
+ const authInfo = this.assertIsAuthSet();
122
82
  if (!webcrypto || !webcrypto.getRandomValues) {
123
83
  throw new Error('Web crypto is not available. Ensure you have imported the shim from @getpara/react-native-wallet.');
124
84
  }
125
85
  const userHandle = new Uint8Array(32);
126
86
  webcrypto.getRandomValues(userHandle);
127
87
  const userHandleEncoded = base64url.encode(userHandle);
128
- const { identifier: displayIdentifier } = extractAuthInfo(auth, { isRequired: true });
129
88
  const requestJson = {
130
89
  authenticatorSelection: {
131
90
  authenticatorAttachment: 'platform',
@@ -139,8 +98,8 @@ export class ParaMobile extends ParaCore {
139
98
  },
140
99
  user: {
141
100
  id: userHandleEncoded,
142
- name: displayIdentifier,
143
- displayName: displayIdentifier,
101
+ name: authInfo.identifier,
102
+ displayName: authInfo.identifier,
144
103
  },
145
104
  pubKeyCredParams: [
146
105
  {
@@ -169,15 +128,15 @@ export class ParaMobile extends ParaCore {
169
128
  const publicKeyHex = getPublicKeyHex(keyPair);
170
129
  const encryptionKeyHash = getSHA256HashHex(userHandleEncoded);
171
130
  const encryptedPrivateKeyHex = yield encryptPrivateKey(keyPair, userHandleEncoded);
172
- const session = yield this.ctx.client.touchSession();
173
- yield this.ctx.client.patchSessionPublicKey(session.data.partnerId, this.getUserId(), biometricsId, {
131
+ const { partnerId } = yield this.ctx.client.touchSession();
132
+ yield this.ctx.client.patchSessionPublicKey(partnerId, userId, authState.passkeyId, {
174
133
  publicKey: resultJson.id,
175
134
  sigDerivedPublicKey: publicKeyHex,
176
135
  cosePublicKey,
177
136
  clientDataJSON,
178
137
  status: PublicKeyStatus.COMPLETE,
179
138
  });
180
- yield this.ctx.client.uploadEncryptedWalletPrivateKey(this.getUserId(), encryptedPrivateKeyHex, encryptionKeyHash, resultJson.id);
139
+ yield this.ctx.client.uploadEncryptedWalletPrivateKey(userId, encryptedPrivateKeyHex, encryptionKeyHash, resultJson.id);
181
140
  });
182
141
  }
183
142
  /**
@@ -185,12 +144,11 @@ export class ParaMobile extends ParaCore {
185
144
  * @param {AuthParams} params - The authentication parameters.
186
145
  * @returns {Promise<void>}
187
146
  */
188
- login(_a) {
147
+ login() {
189
148
  return __awaiter(this, void 0, void 0, function* () {
190
- var auth = __rest(_a, []);
191
- yield this.logout();
192
- const authInfo = extractAuthInfo(auth, { isRequired: true });
193
- const { challenge, allowedPublicKeys } = yield this.ctx.client.getWebChallenge(authInfo.auth);
149
+ this.assertIsAuthSet();
150
+ const userId = this.assertUserId();
151
+ const { challenge, allowedPublicKeys } = yield this.ctx.client.getWebChallenge({ userId });
194
152
  const requestJson = {
195
153
  challenge,
196
154
  timeout: 60000,
@@ -205,9 +163,9 @@ export class ParaMobile extends ParaCore {
205
163
  else {
206
164
  resultJson = result;
207
165
  }
208
- const session = yield this.ctx.client.touchSession();
166
+ const { partnerId } = yield this.ctx.client.touchSession();
209
167
  const publicKey = resultJson.id;
210
- const verifyWebChallengeResult = yield this.ctx.client.verifyWebChallenge(session.data.partnerId, {
168
+ const verifyWebChallengeResult = yield this.ctx.client.verifyWebChallenge(partnerId, {
211
169
  publicKey,
212
170
  signature: {
213
171
  clientDataJSON: resultJson.response.clientDataJSON,
@@ -215,17 +173,8 @@ export class ParaMobile extends ParaCore {
215
173
  signature: resultJson.response.signature,
216
174
  },
217
175
  });
218
- const userId = verifyWebChallengeResult.data.userId;
219
- yield this.setUserId(userId);
220
- const { user } = yield this.ctx.client.getUser(userId);
221
- if (user.phone) {
222
- yield this.setPhoneNumber(user.phone.number, user.phone.countryCode);
223
- }
224
- if (user.email) {
225
- yield this.setEmail(user.email);
226
- }
227
- if (user.farcasterUsername) {
228
- yield this.setFarcasterUsername(user.farcasterUsername);
176
+ if (userId !== verifyWebChallengeResult.userId) {
177
+ throw new Error('User ID mismatch');
229
178
  }
230
179
  const encryptedSharesResult = yield this.ctx.client.getBiometricKeyshares(userId, resultJson.id);
231
180
  const encryptionKeyHash = getSHA256HashHex(resultJson.response.userHandle);
@@ -254,16 +203,7 @@ export class ParaMobile extends ParaCore {
254
203
  type: desiredWallet.type || undefined,
255
204
  };
256
205
  }
257
- const currentWalletIds = {};
258
- for (const wallet of Object.values(walletsToInsert)) {
259
- const { id, type } = wallet;
260
- const currentIdsForType = currentWalletIds[type || 'EVM'] || [];
261
- currentWalletIds[type || 'EVM'] = [...currentIdsForType, id];
262
- }
263
206
  yield this.setWallets(walletsToInsert);
264
- yield this.setCurrentWalletIds(currentWalletIds, {
265
- sessionLookupId: session.data.sessionLookupId,
266
- });
267
207
  });
268
208
  }
269
209
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpara/react-native-wallet",
3
- "version": "1.7.1",
3
+ "version": "2.0.0-alpha.3",
4
4
  "description": "Para Wallet for React Native",
5
5
  "homepage": "https://getpara.com",
6
6
  "author": "Para Team <hello@getpara.com> (https://getpara.com)",
@@ -22,9 +22,9 @@
22
22
  "compile-signer": "bash ./scripts/compileSigner.sh"
23
23
  },
24
24
  "dependencies": {
25
- "@getpara/core-sdk": "1.7.1",
26
- "@getpara/user-management-client": "1.7.1",
27
- "@getpara/web-sdk": "1.7.1",
25
+ "@getpara/core-sdk": "2.0.0-alpha.3",
26
+ "@getpara/user-management-client": "2.0.0-alpha.3",
27
+ "@getpara/web-sdk": "2.0.0-alpha.3",
28
28
  "@peculiar/webcrypto": "^1.5.0",
29
29
  "@sentry/react-native": "^6.7.0",
30
30
  "node-forge": "1.3.1",
@@ -33,16 +33,16 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@craftzdog/react-native-buffer": "6.0.5",
36
- "@react-native-async-storage/async-storage": "2.1.2",
36
+ "@react-native-async-storage/async-storage": "2.1.0",
37
37
  "@types/node-forge": "1.3.1",
38
38
  "@types/react": "^18.0.31",
39
39
  "@types/react-native": "0.70.0",
40
40
  "@types/text-encoding": "0.0.39",
41
- "react-native-keychain": "10.0.0",
41
+ "react-native-keychain": "9.2.2",
42
42
  "react-native-modpow": "1.1.0",
43
- "react-native-passkey": "3.1.0",
43
+ "react-native-passkey": "3.0.0",
44
44
  "react-native-quick-base64": "2.1.2",
45
- "react-native-quick-crypto": "0.7.12",
45
+ "react-native-quick-crypto": "0.7.11",
46
46
  "typescript": "^5.4.3"
47
47
  },
48
48
  "peerDependencies": {
@@ -60,5 +60,5 @@
60
60
  "publishConfig": {
61
61
  "access": "public"
62
62
  },
63
- "gitHead": "767395af96d4a724946a092de760e9137a73369a"
63
+ "gitHead": "77a1e04b06258842ca9c81e3db2a2b0092517659"
64
64
  }
@@ -1,46 +1,28 @@
1
1
  import { StorageUtils } from '@getpara/web-sdk';
2
2
  import RNAsyncStorage from '@react-native-async-storage/async-storage';
3
3
 
4
+ /**
5
+ * Implements `StorageUtils` using React Native Async Storage.
6
+ */
4
7
  export class AsyncStorage implements StorageUtils {
5
- async get(key: string): Promise<string | null> {
6
- try {
7
- return await RNAsyncStorage.getItem(key);
8
- } catch (error) {
9
- console.warn('Error retrieving stored item:', error);
10
- return null;
8
+ async clear(prefix: string): Promise<void> {
9
+ const keys = await RNAsyncStorage.getAllKeys();
10
+ for (const key of keys) {
11
+ if (key.startsWith(prefix)) {
12
+ await RNAsyncStorage.removeItem(key);
13
+ }
11
14
  }
12
15
  }
13
16
 
14
- async set(key: string, value: string): Promise<void> {
15
- try {
16
- await RNAsyncStorage.setItem(key, value);
17
- } catch (error) {
18
- console.warn(`Error storing key ${key}:`, error);
19
- }
17
+ async get(key: string): Promise<string | null> {
18
+ return RNAsyncStorage.getItem(key);
20
19
  }
21
20
 
22
21
  async removeItem(key: string): Promise<void> {
23
- try {
24
- await RNAsyncStorage.removeItem(key);
25
- } catch (error) {
26
- console.warn(`Error removing key ${key}:`, error);
27
- }
22
+ await RNAsyncStorage.removeItem(key);
28
23
  }
29
24
 
30
- async clear(prefix: string): Promise<void> {
31
- try {
32
- const keys = await RNAsyncStorage.getAllKeys();
33
- for (const key of keys) {
34
- if (key.startsWith(prefix)) {
35
- try {
36
- await RNAsyncStorage.removeItem(key);
37
- } catch (error) {
38
- console.warn(`Error clearing key ${key}:`, error);
39
- }
40
- }
41
- }
42
- } catch (error) {
43
- console.warn(`Error getting keys for prefix ${prefix}:`, error);
44
- }
25
+ async set(key: string, value: string): Promise<void> {
26
+ await RNAsyncStorage.setItem(key, value);
45
27
  }
46
28
  }
@@ -2,7 +2,21 @@ import { StorageUtils } from '@getpara/web-sdk';
2
2
  import Keychain from 'react-native-keychain';
3
3
 
4
4
  const USERNAME = '@CAPSULE';
5
+ const KEYCHAIN_USER_CANCELLED_ERRORS = [
6
+ 'user canceled the operation',
7
+ 'error: code: 13, msg: cancel',
8
+ 'error: code: 10, msg: fingerprint operation canceled by the user',
9
+ ];
5
10
 
11
+ function isUserCancelledError(error: Error) {
12
+ return KEYCHAIN_USER_CANCELLED_ERRORS.some(userCancelledError =>
13
+ error.toString().toLowerCase().includes(userCancelledError),
14
+ );
15
+ }
16
+
17
+ /**
18
+ * Implements `StorageUtils` using React Native `Keychain`.
19
+ */
6
20
  export class KeychainStorage implements StorageUtils {
7
21
  async get(key: string): Promise<string | null> {
8
22
  try {
@@ -14,49 +28,32 @@ export class KeychainStorage implements StorageUtils {
14
28
  }
15
29
  return item.password;
16
30
  } catch (error) {
17
- console.warn('Error retrieving stored item:', error);
18
- return null;
31
+ if (error instanceof Error && !isUserCancelledError(error)) {
32
+ // triggered when biometry verification fails and user cancels the action
33
+ throw new Error('Error retrieving stored item ' + error.message);
34
+ }
35
+ throw error;
19
36
  }
20
37
  }
21
-
22
38
  async set(key: string, value: string): Promise<void> {
23
- try {
24
- const result = await Keychain.setGenericPassword(USERNAME, value, {
25
- service: key,
26
- accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
27
- securityLevel: Keychain.SECURITY_LEVEL.ANY,
28
- storage: Keychain.STORAGE_TYPE.AES_GCM_NO_AUTH,
29
- });
30
- if (!result) {
31
- console.warn(`Failed to store key ${key}`);
32
- }
33
- } catch (error) {
34
- console.warn(`Error storing key ${key}:`, error);
39
+ const result = await Keychain.setGenericPassword(USERNAME, value, {
40
+ service: key,
41
+ accessible: Keychain.ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
42
+ securityLevel: Keychain.SECURITY_LEVEL.ANY,
43
+ });
44
+ if (!result) {
45
+ throw new Error('Failed to store key ' + key);
35
46
  }
36
47
  }
37
-
38
48
  async removeItem(key: string): Promise<void> {
39
- try {
40
- await Keychain.resetGenericPassword({ service: key });
41
- } catch (error) {
42
- console.warn(`Error removing key ${key}:`, error);
43
- }
49
+ await Keychain.resetGenericPassword({ service: key });
44
50
  }
45
-
46
51
  async clear(prefix: string): Promise<void> {
47
- try {
48
- const services = await Keychain.getAllGenericPasswordServices();
49
- for (const key of services) {
50
- if (key && key.startsWith(prefix)) {
51
- try {
52
- await Keychain.resetGenericPassword({ service: key });
53
- } catch (error) {
54
- console.warn(`Error clearing key ${key}:`, error);
55
- }
56
- }
52
+ const services = await Keychain.getAllGenericPasswordServices();
53
+ for (const key of services) {
54
+ if (key && key.startsWith(prefix)) {
55
+ await Keychain.resetGenericPassword({ service: key });
57
56
  }
58
- } catch (error) {
59
- console.warn(`Error getting services for prefix ${prefix}:`, error);
60
57
  }
61
58
  }
62
59
  }
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AuthStateSignup,
2
3
  ConstructorOpts,
3
4
  ParaCore,
4
5
  Environment,
@@ -22,7 +23,7 @@ import {
22
23
  PasskeyGetRequest,
23
24
  PasskeyGetResult,
24
25
  } from 'react-native-passkey';
25
- import { Auth, CurrentWalletIds, extractAuthInfo, PublicKeyStatus, WalletScheme } from '@getpara/user-management-client';
26
+ import { PublicKeyStatus, WalletScheme } from '@getpara/user-management-client';
26
27
  import { setEnv } from '../config.js';
27
28
  import base64url from 'base64url';
28
29
  import { webcrypto } from 'crypto';
@@ -38,6 +39,8 @@ const RS256_ALGORITHM = -257;
38
39
  * const para = new ParaMobile(Environment.BETA, "api_key");
39
40
  */
40
41
  export class ParaMobile extends ParaCore {
42
+ isNativePasskey = true;
43
+
41
44
  private relyingPartyId: string;
42
45
  /**
43
46
  * Creates an instance of ParaMobile.
@@ -46,7 +49,7 @@ export class ParaMobile extends ParaCore {
46
49
  * @param {string} [relyingPartyId] - The relying party ID for WebAuthn.
47
50
  * @param {ConstructorOpts} [opts] - Additional constructor options.
48
51
  */
49
- constructor(env: Environment, apiKey?: string, relyingPartyId?: string, opts?: ConstructorOpts) {
52
+ constructor(env: Environment, apiKey: string, relyingPartyId?: string, opts?: ConstructorOpts) {
50
53
  super(env, apiKey, opts);
51
54
 
52
55
  // starting with non-prod to see what kind of errors we get and if sensitive data is tracked
@@ -83,40 +86,20 @@ export class ParaMobile extends ParaCore {
83
86
  return new ReactNativeUtils();
84
87
  }
85
88
 
86
- /**
87
- * Verifies an email and returns the biometrics ID.
88
- * @param {string} verificationCode - The verification code sent to the email.
89
- * @returns {Promise<string>} The biometrics ID.
90
- */
91
- async verifyEmailBiometricsId({ verificationCode }: { verificationCode: string }): Promise<string> {
92
- const webAuthCreateUrl = await super.verifyEmail({ verificationCode });
93
- const segments = webAuthCreateUrl.split('/');
94
- const segments2 = segments[segments.length - 1]!.split('?');
95
- const biometricsId = segments2[0]!;
96
-
97
- return biometricsId;
98
- }
99
-
100
- /**
101
- * Verifies a phone number and returns the biometrics ID.
102
- * @param {string} verificationCode - The verification code sent to the phone.
103
- * @returns {Promise<string>} The biometrics ID.
104
- */
105
- async verifyPhoneBiometricsId({ verificationCode }: { verificationCode: string }): Promise<string> {
106
- const webAuthCreateUrl = await super.verifyPhone({ verificationCode });
107
- const segments = webAuthCreateUrl.split('/');
108
- const segments2 = segments[segments.length - 1]!.split('?');
109
- const biometricsId = segments2[0]!;
110
-
111
- return biometricsId;
112
- }
113
89
  /**
114
90
  * Registers a passkey for the user.
115
91
  * @param {Auth<'email'> | Auth<'phone'>} auth - The user's authentication details
116
92
  * @param {string} biometricsId - The biometrics ID obtained from verification.
117
93
  * @returns {Promise<void>}
118
94
  */
119
- async registerPasskey({ biometricsId, ...auth }: { biometricsId: string } & (Auth<'email'> | Auth<'phone'>)) {
95
+ async registerPasskey(authState: AuthStateSignup) {
96
+ if (!authState.passkeyId) {
97
+ throw new Error('Passkey ID not found. Make sure you have enabled passkey logins in the Para Developer Portal.');
98
+ }
99
+
100
+ const userId = this.assertUserId();
101
+ const authInfo = this.assertIsAuthSet();
102
+
120
103
  if (!webcrypto || !webcrypto.getRandomValues) {
121
104
  throw new Error('Web crypto is not available. Ensure you have imported the shim from @getpara/react-native-wallet.');
122
105
  }
@@ -124,8 +107,6 @@ export class ParaMobile extends ParaCore {
124
107
  webcrypto.getRandomValues(userHandle);
125
108
  const userHandleEncoded = base64url.encode(userHandle as any);
126
109
 
127
- const { identifier: displayIdentifier } = extractAuthInfo(auth, { isRequired: true });
128
-
129
110
  const requestJson: PasskeyCreateRequest = {
130
111
  authenticatorSelection: {
131
112
  authenticatorAttachment: 'platform' as any,
@@ -139,8 +120,8 @@ export class ParaMobile extends ParaCore {
139
120
  },
140
121
  user: {
141
122
  id: userHandleEncoded,
142
- name: displayIdentifier,
143
- displayName: displayIdentifier,
123
+ name: authInfo.identifier,
124
+ displayName: authInfo.identifier,
144
125
  },
145
126
  pubKeyCredParams: [
146
127
  {
@@ -174,8 +155,8 @@ export class ParaMobile extends ParaCore {
174
155
  const encryptionKeyHash = getSHA256HashHex(userHandleEncoded);
175
156
  const encryptedPrivateKeyHex = await encryptPrivateKey(keyPair, userHandleEncoded);
176
157
 
177
- const session = await this.ctx.client.touchSession();
178
- await this.ctx.client.patchSessionPublicKey(session.data.partnerId, this.getUserId()!, biometricsId, {
158
+ const { partnerId } = await this.ctx.client.touchSession();
159
+ await this.ctx.client.patchSessionPublicKey(partnerId, userId, authState.passkeyId, {
179
160
  publicKey: resultJson.id,
180
161
  sigDerivedPublicKey: publicKeyHex,
181
162
  cosePublicKey,
@@ -183,12 +164,7 @@ export class ParaMobile extends ParaCore {
183
164
  status: PublicKeyStatus.COMPLETE,
184
165
  });
185
166
 
186
- await this.ctx.client.uploadEncryptedWalletPrivateKey(
187
- this.getUserId()!,
188
- encryptedPrivateKeyHex,
189
- encryptionKeyHash,
190
- resultJson.id,
191
- );
167
+ await this.ctx.client.uploadEncryptedWalletPrivateKey(userId, encryptedPrivateKeyHex, encryptionKeyHash, resultJson.id);
192
168
  }
193
169
 
194
170
  /**
@@ -196,12 +172,11 @@ export class ParaMobile extends ParaCore {
196
172
  * @param {AuthParams} params - The authentication parameters.
197
173
  * @returns {Promise<void>}
198
174
  */
199
- async login({ ...auth }: Auth<'email'> | Auth<'phone'>): Promise<void> {
200
- await this.logout();
201
-
202
- const authInfo = extractAuthInfo(auth, { isRequired: true });
175
+ async login(): Promise<void> {
176
+ this.assertIsAuthSet();
177
+ const userId = this.assertUserId();
203
178
 
204
- const { challenge, allowedPublicKeys } = await this.ctx.client.getWebChallenge(authInfo.auth);
179
+ const { challenge, allowedPublicKeys } = await this.ctx.client.getWebChallenge({ userId });
205
180
 
206
181
  const requestJson: PasskeyGetRequest = {
207
182
  challenge,
@@ -220,9 +195,9 @@ export class ParaMobile extends ParaCore {
220
195
  resultJson = result;
221
196
  }
222
197
 
223
- const session = await this.ctx.client.touchSession();
198
+ const { partnerId } = await this.ctx.client.touchSession();
224
199
  const publicKey = resultJson.id;
225
- const verifyWebChallengeResult = await this.ctx.client.verifyWebChallenge(session.data.partnerId, {
200
+ const verifyWebChallengeResult = await this.ctx.client.verifyWebChallenge(partnerId, {
226
201
  publicKey,
227
202
  signature: {
228
203
  clientDataJSON: resultJson.response.clientDataJSON,
@@ -231,22 +206,8 @@ export class ParaMobile extends ParaCore {
231
206
  },
232
207
  });
233
208
 
234
- const userId = verifyWebChallengeResult.data.userId;
235
-
236
- await this.setUserId(userId);
237
-
238
- const { user } = await this.ctx.client.getUser(userId);
239
-
240
- if (user.phone) {
241
- await this.setPhoneNumber(user.phone.number, user.phone.countryCode);
242
- }
243
-
244
- if (user.email) {
245
- await this.setEmail(user.email);
246
- }
247
-
248
- if (user.farcasterUsername) {
249
- await this.setFarcasterUsername(user.farcasterUsername);
209
+ if (userId !== verifyWebChallengeResult.userId) {
210
+ throw new Error('User ID mismatch');
250
211
  }
251
212
 
252
213
  const encryptedSharesResult = await this.ctx.client.getBiometricKeyshares(userId, resultJson.id);
@@ -293,16 +254,6 @@ export class ParaMobile extends ParaCore {
293
254
  };
294
255
  }
295
256
 
296
- const currentWalletIds: CurrentWalletIds = {};
297
- for (const wallet of Object.values(walletsToInsert)) {
298
- const { id, type } = wallet;
299
- const currentIdsForType = currentWalletIds[type || 'EVM'] || [];
300
- currentWalletIds[type || 'EVM'] = [...currentIdsForType, id];
301
- }
302
-
303
257
  await this.setWallets(walletsToInsert);
304
- await this.setCurrentWalletIds(currentWalletIds, {
305
- sessionLookupId: session.data.sessionLookupId,
306
- });
307
258
  }
308
259
  }