@getpara/react-native-wallet 2.23.0 → 2.24.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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/react-native/ParaMobile.js +19 -2
- package/dist/react-native/errors.d.ts +18 -0
- package/dist/react-native/errors.js +244 -0
- package/package.json +7 -7
- package/src/index.ts +1 -0
- package/src/react-native/ParaMobile.ts +17 -2
- package/src/react-native/errors.ts +297 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from '@getpara/web-sdk';
|
|
2
2
|
export { ParaMobile } from './react-native/ParaMobile.js';
|
|
3
|
+
export { ParaPasskeyError, type ParaPasskeyErrorCode, PARA_PASSKEY_ERROR_CODES } from './react-native/errors.js';
|
|
3
4
|
export type * from '@getpara/viem-v2-integration/aa';
|
|
4
5
|
export * from './provider/index.js';
|
|
5
6
|
export * from '@getpara/react-core';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from '@getpara/web-sdk';
|
|
2
2
|
export { ParaMobile } from './react-native/ParaMobile.js';
|
|
3
|
+
export { ParaPasskeyError, PARA_PASSKEY_ERROR_CODES } from './react-native/errors.js';
|
|
3
4
|
// React Native provider (wraps ParaProviderCore with AsyncStorage persistence)
|
|
4
5
|
export * from './provider/index.js';
|
|
5
6
|
// Core provider hooks and config types — platform-agnostic, work with any ParaCore subclass
|
|
@@ -11,6 +11,7 @@ import { ParaCore, Environment, decryptPrivateKeyAndDecryptShare, encryptPrivate
|
|
|
11
11
|
import { ReactNativeUtils } from './ReactNativeUtils.js';
|
|
12
12
|
import { Passkey, } from 'react-native-passkey';
|
|
13
13
|
import { AuthMethodStatus } from '@getpara/user-management-client';
|
|
14
|
+
import { mapPasskeyError, logPasskeyDiagnostic } from './errors.js';
|
|
14
15
|
import { setEnv } from '../config.js';
|
|
15
16
|
import base64url from 'base64url';
|
|
16
17
|
const ES256_ALGORITHM = -7;
|
|
@@ -155,7 +156,15 @@ export class ParaMobile extends ParaCore {
|
|
|
155
156
|
timeout: 60000,
|
|
156
157
|
challenge: base64url.encode('somechallenge'),
|
|
157
158
|
};
|
|
158
|
-
|
|
159
|
+
let result;
|
|
160
|
+
try {
|
|
161
|
+
result = yield Passkey.create(requestJson);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
const enriched = mapPasskeyError(error);
|
|
165
|
+
logPasskeyDiagnostic(enriched, 'registration', this.relyingPartyId);
|
|
166
|
+
throw enriched;
|
|
167
|
+
}
|
|
159
168
|
let resultJson;
|
|
160
169
|
if (typeof result === 'string') {
|
|
161
170
|
resultJson = JSON.parse(result);
|
|
@@ -195,7 +204,15 @@ export class ParaMobile extends ParaCore {
|
|
|
195
204
|
rpId: this.relyingPartyId,
|
|
196
205
|
allowCredentials: (allowedPublicKeys === null || allowedPublicKeys === void 0 ? void 0 : allowedPublicKeys[0]) ? [{ type: 'public-key', id: allowedPublicKeys[0] }] : [],
|
|
197
206
|
};
|
|
198
|
-
|
|
207
|
+
let result;
|
|
208
|
+
try {
|
|
209
|
+
result = yield Passkey.get(requestJson);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
const enriched = mapPasskeyError(error);
|
|
213
|
+
logPasskeyDiagnostic(enriched, 'login', this.relyingPartyId);
|
|
214
|
+
throw enriched;
|
|
215
|
+
}
|
|
199
216
|
let resultJson;
|
|
200
217
|
if (typeof result === 'string') {
|
|
201
218
|
resultJson = JSON.parse(result);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const PARA_PASSKEY_ERROR_CODES: readonly ["USER_CANCELLED", "TIMED_OUT", "BAD_CONFIGURATION", "NOT_CONFIGURED", "RP_ID_MISMATCH", "NOT_SUPPORTED", "NO_CREDENTIALS", "REQUEST_FAILED", "INTERRUPTED", "UNKNOWN"];
|
|
2
|
+
export type ParaPasskeyErrorCode = (typeof PARA_PASSKEY_ERROR_CODES)[number];
|
|
3
|
+
export declare class ParaPasskeyError extends Error {
|
|
4
|
+
readonly code: ParaPasskeyErrorCode;
|
|
5
|
+
readonly platform: 'ios' | 'android' | 'unknown';
|
|
6
|
+
readonly suggestion: string;
|
|
7
|
+
readonly docsUrl: string;
|
|
8
|
+
constructor(params: {
|
|
9
|
+
code: ParaPasskeyErrorCode;
|
|
10
|
+
message: string;
|
|
11
|
+
platform: 'ios' | 'android' | 'unknown';
|
|
12
|
+
suggestion: string;
|
|
13
|
+
docsUrl: string;
|
|
14
|
+
cause?: unknown;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export declare function mapPasskeyError(error: unknown): ParaPasskeyError;
|
|
18
|
+
export declare function logPasskeyDiagnostic(error: ParaPasskeyError, operation: string, relyingPartyId: string): void;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
export const PARA_PASSKEY_ERROR_CODES = [
|
|
3
|
+
'USER_CANCELLED',
|
|
4
|
+
'TIMED_OUT',
|
|
5
|
+
'BAD_CONFIGURATION',
|
|
6
|
+
'NOT_CONFIGURED',
|
|
7
|
+
'RP_ID_MISMATCH',
|
|
8
|
+
'NOT_SUPPORTED',
|
|
9
|
+
'NO_CREDENTIALS',
|
|
10
|
+
'REQUEST_FAILED',
|
|
11
|
+
'INTERRUPTED',
|
|
12
|
+
'UNKNOWN',
|
|
13
|
+
];
|
|
14
|
+
const DOCS_BASE_URL = 'https://docs.getpara.com/v2/react-native/error-codes';
|
|
15
|
+
export class ParaPasskeyError extends Error {
|
|
16
|
+
constructor(params) {
|
|
17
|
+
super(params.message);
|
|
18
|
+
Object.setPrototypeOf(this, ParaPasskeyError.prototype);
|
|
19
|
+
this.name = 'ParaPasskeyError';
|
|
20
|
+
this.code = params.code;
|
|
21
|
+
this.platform = params.platform;
|
|
22
|
+
this.suggestion = params.suggestion;
|
|
23
|
+
this.docsUrl = params.docsUrl;
|
|
24
|
+
if (params.cause !== undefined) {
|
|
25
|
+
this.cause = params.cause;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function isPasskeyError(error) {
|
|
30
|
+
return (typeof error === 'object' &&
|
|
31
|
+
error !== null &&
|
|
32
|
+
'error' in error &&
|
|
33
|
+
'message' in error &&
|
|
34
|
+
typeof error.error === 'string' &&
|
|
35
|
+
typeof error.message === 'string');
|
|
36
|
+
}
|
|
37
|
+
function detectPlatform() {
|
|
38
|
+
if (Platform.OS === 'ios')
|
|
39
|
+
return 'ios';
|
|
40
|
+
if (Platform.OS === 'android')
|
|
41
|
+
return 'android';
|
|
42
|
+
return 'unknown';
|
|
43
|
+
}
|
|
44
|
+
function docsUrlForCode(code) {
|
|
45
|
+
const fragment = code.toLowerCase();
|
|
46
|
+
return `${DOCS_BASE_URL}#${fragment}`;
|
|
47
|
+
}
|
|
48
|
+
export function mapPasskeyError(error) {
|
|
49
|
+
const platform = detectPlatform();
|
|
50
|
+
if (!isPasskeyError(error)) {
|
|
51
|
+
const code = 'UNKNOWN';
|
|
52
|
+
return new ParaPasskeyError({
|
|
53
|
+
code,
|
|
54
|
+
message: error instanceof Error ? error.message : String(error),
|
|
55
|
+
platform,
|
|
56
|
+
suggestion: "An unexpected error occurred during the passkey operation. Check the 'cause' property for details.",
|
|
57
|
+
docsUrl: docsUrlForCode(code),
|
|
58
|
+
cause: error,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const errorField = error.error;
|
|
62
|
+
const messageField = error.message;
|
|
63
|
+
switch (errorField) {
|
|
64
|
+
case 'UserCancelled': {
|
|
65
|
+
const code = 'USER_CANCELLED';
|
|
66
|
+
return new ParaPasskeyError({
|
|
67
|
+
code,
|
|
68
|
+
message: messageField,
|
|
69
|
+
platform,
|
|
70
|
+
suggestion: 'The user dismissed the passkey prompt. Handle this gracefully and allow the user to retry.',
|
|
71
|
+
docsUrl: docsUrlForCode(code),
|
|
72
|
+
cause: error,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
case 'TimedOut': {
|
|
76
|
+
const code = 'TIMED_OUT';
|
|
77
|
+
return new ParaPasskeyError({
|
|
78
|
+
code,
|
|
79
|
+
message: messageField,
|
|
80
|
+
platform,
|
|
81
|
+
suggestion: 'The passkey operation timed out. Ensure the user completes the biometric prompt within the timeout period.',
|
|
82
|
+
docsUrl: docsUrlForCode(code),
|
|
83
|
+
cause: error,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
case 'BadConfiguration': {
|
|
87
|
+
const code = 'BAD_CONFIGURATION';
|
|
88
|
+
return new ParaPasskeyError({
|
|
89
|
+
code,
|
|
90
|
+
message: messageField,
|
|
91
|
+
platform,
|
|
92
|
+
suggestion: 'App entitlements or associated domains are misconfigured. Verify your webcredentials entries and confirm your Team ID + Bundle ID is registered in the Developer Portal.',
|
|
93
|
+
docsUrl: docsUrlForCode(code),
|
|
94
|
+
cause: error,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
case 'NotConfigured': {
|
|
98
|
+
const code = 'NOT_CONFIGURED';
|
|
99
|
+
return new ParaPasskeyError({
|
|
100
|
+
code,
|
|
101
|
+
message: messageField,
|
|
102
|
+
platform,
|
|
103
|
+
suggestion: 'The credential provider is not configured. Ensure Google Play Services is available and a Google account is signed in on the device.',
|
|
104
|
+
docsUrl: docsUrlForCode(code),
|
|
105
|
+
cause: error,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
case 'NotSupported': {
|
|
109
|
+
const code = 'NOT_SUPPORTED';
|
|
110
|
+
return new ParaPasskeyError({
|
|
111
|
+
code,
|
|
112
|
+
message: messageField,
|
|
113
|
+
platform,
|
|
114
|
+
suggestion: 'Passkeys are not supported on this device. iOS 16+ or Android API 28+ is required.',
|
|
115
|
+
docsUrl: docsUrlForCode(code),
|
|
116
|
+
cause: error,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
case 'NoCredentials': {
|
|
120
|
+
const code = 'NO_CREDENTIALS';
|
|
121
|
+
return new ParaPasskeyError({
|
|
122
|
+
code,
|
|
123
|
+
message: messageField,
|
|
124
|
+
platform,
|
|
125
|
+
suggestion: 'No passkeys found for this user on this device. The user may need to register a passkey first or is on a different device.',
|
|
126
|
+
docsUrl: docsUrlForCode(code),
|
|
127
|
+
cause: error,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
case 'RequestFailed': {
|
|
131
|
+
const code = 'REQUEST_FAILED';
|
|
132
|
+
return new ParaPasskeyError({
|
|
133
|
+
code,
|
|
134
|
+
message: messageField,
|
|
135
|
+
platform,
|
|
136
|
+
suggestion: 'The passkey request failed. On iOS, check associated domains and Team ID + Bundle ID registration. On Android, verify your SHA-256 fingerprint in the Developer Portal.',
|
|
137
|
+
docsUrl: docsUrlForCode(code),
|
|
138
|
+
cause: error,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
case 'Interrupted': {
|
|
142
|
+
const code = 'INTERRUPTED';
|
|
143
|
+
return new ParaPasskeyError({
|
|
144
|
+
code,
|
|
145
|
+
message: messageField,
|
|
146
|
+
platform,
|
|
147
|
+
suggestion: 'The passkey operation was interrupted and may be retried.',
|
|
148
|
+
docsUrl: docsUrlForCode(code),
|
|
149
|
+
cause: error,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
case 'InvalidChallenge': {
|
|
153
|
+
const code = 'BAD_CONFIGURATION';
|
|
154
|
+
return new ParaPasskeyError({
|
|
155
|
+
code,
|
|
156
|
+
message: messageField,
|
|
157
|
+
platform,
|
|
158
|
+
suggestion: 'The challenge provided to the passkey request was invalid. This is likely an SDK internal issue.',
|
|
159
|
+
docsUrl: docsUrlForCode(code),
|
|
160
|
+
cause: error,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
case 'InvalidUser':
|
|
164
|
+
case 'InvalidUserId': {
|
|
165
|
+
const code = 'BAD_CONFIGURATION';
|
|
166
|
+
return new ParaPasskeyError({
|
|
167
|
+
code,
|
|
168
|
+
message: messageField,
|
|
169
|
+
platform,
|
|
170
|
+
suggestion: 'The user ID provided to the passkey request was invalid. This is likely an SDK internal issue.',
|
|
171
|
+
docsUrl: docsUrlForCode(code),
|
|
172
|
+
cause: error,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
case 'HandlerUndefined': {
|
|
176
|
+
const code = 'NOT_SUPPORTED';
|
|
177
|
+
return new ParaPasskeyError({
|
|
178
|
+
code,
|
|
179
|
+
message: messageField,
|
|
180
|
+
platform,
|
|
181
|
+
suggestion: 'The passkey handler is not available. This typically means the iOS version does not support passkeys (requires iOS 16+).',
|
|
182
|
+
docsUrl: docsUrlForCode(code),
|
|
183
|
+
cause: error,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
case 'Native error': {
|
|
187
|
+
const gpsMatch = /\[(\d+)\]/.exec(messageField);
|
|
188
|
+
if (gpsMatch && gpsMatch[1] === '50152') {
|
|
189
|
+
const code = 'RP_ID_MISMATCH';
|
|
190
|
+
return new ParaPasskeyError({
|
|
191
|
+
code,
|
|
192
|
+
message: messageField,
|
|
193
|
+
platform,
|
|
194
|
+
suggestion: "Your app's signing certificate SHA-256 fingerprint doesn't match what's registered in the Developer Portal. Debug and release builds use different signing keys. Run 'keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android' to get your debug fingerprint, then register it in the Developer Portal.",
|
|
195
|
+
docsUrl: docsUrlForCode(code),
|
|
196
|
+
cause: error,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const code = 'UNKNOWN';
|
|
200
|
+
return new ParaPasskeyError({
|
|
201
|
+
code,
|
|
202
|
+
message: messageField,
|
|
203
|
+
platform,
|
|
204
|
+
suggestion: "An unrecognized native passkey error occurred. Check the 'cause' property for the original error details.",
|
|
205
|
+
docsUrl: docsUrlForCode(code),
|
|
206
|
+
cause: error,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
case 'UnknownError':
|
|
210
|
+
case 'Unknown error': {
|
|
211
|
+
const code = 'UNKNOWN';
|
|
212
|
+
return new ParaPasskeyError({
|
|
213
|
+
code,
|
|
214
|
+
message: messageField,
|
|
215
|
+
platform,
|
|
216
|
+
suggestion: "An unknown passkey error occurred. Check the 'cause' property for details.",
|
|
217
|
+
docsUrl: docsUrlForCode(code),
|
|
218
|
+
cause: error,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
default: {
|
|
222
|
+
const code = 'UNKNOWN';
|
|
223
|
+
return new ParaPasskeyError({
|
|
224
|
+
code,
|
|
225
|
+
message: messageField,
|
|
226
|
+
platform,
|
|
227
|
+
suggestion: "An unexpected error occurred during the passkey operation. Check the 'cause' property for details.",
|
|
228
|
+
docsUrl: docsUrlForCode(code),
|
|
229
|
+
cause: error,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
export function logPasskeyDiagnostic(error, operation, relyingPartyId) {
|
|
235
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
236
|
+
console.error(`[Para] Passkey ${operation} failed\n` +
|
|
237
|
+
` Code: ${error.code}\n` +
|
|
238
|
+
` Platform: ${error.platform}\n` +
|
|
239
|
+
` Message: ${error.message}\n` +
|
|
240
|
+
` RP ID: ${relyingPartyId}\n` +
|
|
241
|
+
` Suggestion: ${error.suggestion}\n` +
|
|
242
|
+
` Docs: ${error.docsUrl}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
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.24.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.24.0",
|
|
8
|
+
"@getpara/react-core": "2.24.0",
|
|
9
|
+
"@getpara/user-management-client": "2.24.0",
|
|
10
|
+
"@getpara/viem-v2-integration": "2.24.0",
|
|
11
|
+
"@getpara/web-sdk": "2.24.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": "3ea729c9a5e550c2053c5fff07d1ca7d8a8f9ce9"
|
|
226
226
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from '@getpara/web-sdk';
|
|
2
2
|
export { ParaMobile } from './react-native/ParaMobile.js';
|
|
3
|
+
export { ParaPasskeyError, type ParaPasskeyErrorCode, PARA_PASSKEY_ERROR_CODES } from './react-native/errors.js';
|
|
3
4
|
|
|
4
5
|
// Smart Account types — type-only re-export so Metro doesn't bundle viem for non-AA users
|
|
5
6
|
export type * from '@getpara/viem-v2-integration/aa';
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
PasskeyGetResult,
|
|
25
25
|
} from 'react-native-passkey';
|
|
26
26
|
import { CurrentWalletIds, AuthMethodStatus, TWalletScheme } from '@getpara/user-management-client';
|
|
27
|
+
import { mapPasskeyError, logPasskeyDiagnostic } from './errors.js';
|
|
27
28
|
import { setEnv } from '../config.js';
|
|
28
29
|
import base64url from 'base64url';
|
|
29
30
|
|
|
@@ -196,7 +197,14 @@ export class ParaMobile extends ParaCore {
|
|
|
196
197
|
challenge: base64url.encode('somechallenge'),
|
|
197
198
|
};
|
|
198
199
|
|
|
199
|
-
|
|
200
|
+
let result: PasskeyCreateResult;
|
|
201
|
+
try {
|
|
202
|
+
result = await Passkey.create(requestJson);
|
|
203
|
+
} catch (error: unknown) {
|
|
204
|
+
const enriched = mapPasskeyError(error);
|
|
205
|
+
logPasskeyDiagnostic(enriched, 'registration', this.relyingPartyId);
|
|
206
|
+
throw enriched;
|
|
207
|
+
}
|
|
200
208
|
let resultJson;
|
|
201
209
|
|
|
202
210
|
if (typeof result === 'string') {
|
|
@@ -243,7 +251,14 @@ export class ParaMobile extends ParaCore {
|
|
|
243
251
|
allowCredentials: allowedPublicKeys?.[0] ? [{ type: 'public-key', id: allowedPublicKeys[0] }] : [],
|
|
244
252
|
};
|
|
245
253
|
|
|
246
|
-
|
|
254
|
+
let result: PasskeyGetResult;
|
|
255
|
+
try {
|
|
256
|
+
result = await Passkey.get(requestJson);
|
|
257
|
+
} catch (error: unknown) {
|
|
258
|
+
const enriched = mapPasskeyError(error);
|
|
259
|
+
logPasskeyDiagnostic(enriched, 'login', this.relyingPartyId);
|
|
260
|
+
throw enriched;
|
|
261
|
+
}
|
|
247
262
|
|
|
248
263
|
let resultJson;
|
|
249
264
|
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export const PARA_PASSKEY_ERROR_CODES = [
|
|
4
|
+
'USER_CANCELLED',
|
|
5
|
+
'TIMED_OUT',
|
|
6
|
+
'BAD_CONFIGURATION',
|
|
7
|
+
'NOT_CONFIGURED',
|
|
8
|
+
'RP_ID_MISMATCH',
|
|
9
|
+
'NOT_SUPPORTED',
|
|
10
|
+
'NO_CREDENTIALS',
|
|
11
|
+
'REQUEST_FAILED',
|
|
12
|
+
'INTERRUPTED',
|
|
13
|
+
'UNKNOWN',
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
export type ParaPasskeyErrorCode = (typeof PARA_PASSKEY_ERROR_CODES)[number];
|
|
17
|
+
|
|
18
|
+
const DOCS_BASE_URL = 'https://docs.getpara.com/v2/react-native/error-codes';
|
|
19
|
+
|
|
20
|
+
export class ParaPasskeyError extends Error {
|
|
21
|
+
readonly code: ParaPasskeyErrorCode;
|
|
22
|
+
readonly platform: 'ios' | 'android' | 'unknown';
|
|
23
|
+
readonly suggestion: string;
|
|
24
|
+
readonly docsUrl: string;
|
|
25
|
+
|
|
26
|
+
constructor(params: {
|
|
27
|
+
code: ParaPasskeyErrorCode;
|
|
28
|
+
message: string;
|
|
29
|
+
platform: 'ios' | 'android' | 'unknown';
|
|
30
|
+
suggestion: string;
|
|
31
|
+
docsUrl: string;
|
|
32
|
+
cause?: unknown;
|
|
33
|
+
}) {
|
|
34
|
+
super(params.message);
|
|
35
|
+
Object.setPrototypeOf(this, ParaPasskeyError.prototype);
|
|
36
|
+
this.name = 'ParaPasskeyError';
|
|
37
|
+
this.code = params.code;
|
|
38
|
+
this.platform = params.platform;
|
|
39
|
+
this.suggestion = params.suggestion;
|
|
40
|
+
this.docsUrl = params.docsUrl;
|
|
41
|
+
if (params.cause !== undefined) {
|
|
42
|
+
(this as unknown as { cause: unknown }).cause = params.cause;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface PasskeyErrorShape {
|
|
48
|
+
error: string;
|
|
49
|
+
message: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isPasskeyError(error: unknown): error is PasskeyErrorShape {
|
|
53
|
+
return (
|
|
54
|
+
typeof error === 'object' &&
|
|
55
|
+
error !== null &&
|
|
56
|
+
'error' in error &&
|
|
57
|
+
'message' in error &&
|
|
58
|
+
typeof (error as Record<string, unknown>).error === 'string' &&
|
|
59
|
+
typeof (error as Record<string, unknown>).message === 'string'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function detectPlatform(): 'ios' | 'android' | 'unknown' {
|
|
64
|
+
if (Platform.OS === 'ios') return 'ios';
|
|
65
|
+
if (Platform.OS === 'android') return 'android';
|
|
66
|
+
return 'unknown';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function docsUrlForCode(code: ParaPasskeyErrorCode): string {
|
|
70
|
+
const fragment = code.toLowerCase();
|
|
71
|
+
return `${DOCS_BASE_URL}#${fragment}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function mapPasskeyError(error: unknown): ParaPasskeyError {
|
|
75
|
+
const platform = detectPlatform();
|
|
76
|
+
|
|
77
|
+
if (!isPasskeyError(error)) {
|
|
78
|
+
const code: ParaPasskeyErrorCode = 'UNKNOWN';
|
|
79
|
+
return new ParaPasskeyError({
|
|
80
|
+
code,
|
|
81
|
+
message: error instanceof Error ? error.message : String(error),
|
|
82
|
+
platform,
|
|
83
|
+
suggestion: "An unexpected error occurred during the passkey operation. Check the 'cause' property for details.",
|
|
84
|
+
docsUrl: docsUrlForCode(code),
|
|
85
|
+
cause: error,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const errorField = error.error;
|
|
90
|
+
const messageField = error.message;
|
|
91
|
+
|
|
92
|
+
switch (errorField) {
|
|
93
|
+
case 'UserCancelled': {
|
|
94
|
+
const code: ParaPasskeyErrorCode = 'USER_CANCELLED';
|
|
95
|
+
return new ParaPasskeyError({
|
|
96
|
+
code,
|
|
97
|
+
message: messageField,
|
|
98
|
+
platform,
|
|
99
|
+
suggestion: 'The user dismissed the passkey prompt. Handle this gracefully and allow the user to retry.',
|
|
100
|
+
docsUrl: docsUrlForCode(code),
|
|
101
|
+
cause: error,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
case 'TimedOut': {
|
|
106
|
+
const code: ParaPasskeyErrorCode = 'TIMED_OUT';
|
|
107
|
+
return new ParaPasskeyError({
|
|
108
|
+
code,
|
|
109
|
+
message: messageField,
|
|
110
|
+
platform,
|
|
111
|
+
suggestion:
|
|
112
|
+
'The passkey operation timed out. Ensure the user completes the biometric prompt within the timeout period.',
|
|
113
|
+
docsUrl: docsUrlForCode(code),
|
|
114
|
+
cause: error,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
case 'BadConfiguration': {
|
|
119
|
+
const code: ParaPasskeyErrorCode = 'BAD_CONFIGURATION';
|
|
120
|
+
return new ParaPasskeyError({
|
|
121
|
+
code,
|
|
122
|
+
message: messageField,
|
|
123
|
+
platform,
|
|
124
|
+
suggestion:
|
|
125
|
+
'App entitlements or associated domains are misconfigured. Verify your webcredentials entries and confirm your Team ID + Bundle ID is registered in the Developer Portal.',
|
|
126
|
+
docsUrl: docsUrlForCode(code),
|
|
127
|
+
cause: error,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
case 'NotConfigured': {
|
|
132
|
+
const code: ParaPasskeyErrorCode = 'NOT_CONFIGURED';
|
|
133
|
+
return new ParaPasskeyError({
|
|
134
|
+
code,
|
|
135
|
+
message: messageField,
|
|
136
|
+
platform,
|
|
137
|
+
suggestion:
|
|
138
|
+
'The credential provider is not configured. Ensure Google Play Services is available and a Google account is signed in on the device.',
|
|
139
|
+
docsUrl: docsUrlForCode(code),
|
|
140
|
+
cause: error,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
case 'NotSupported': {
|
|
145
|
+
const code: ParaPasskeyErrorCode = 'NOT_SUPPORTED';
|
|
146
|
+
return new ParaPasskeyError({
|
|
147
|
+
code,
|
|
148
|
+
message: messageField,
|
|
149
|
+
platform,
|
|
150
|
+
suggestion: 'Passkeys are not supported on this device. iOS 16+ or Android API 28+ is required.',
|
|
151
|
+
docsUrl: docsUrlForCode(code),
|
|
152
|
+
cause: error,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case 'NoCredentials': {
|
|
157
|
+
const code: ParaPasskeyErrorCode = 'NO_CREDENTIALS';
|
|
158
|
+
return new ParaPasskeyError({
|
|
159
|
+
code,
|
|
160
|
+
message: messageField,
|
|
161
|
+
platform,
|
|
162
|
+
suggestion:
|
|
163
|
+
'No passkeys found for this user on this device. The user may need to register a passkey first or is on a different device.',
|
|
164
|
+
docsUrl: docsUrlForCode(code),
|
|
165
|
+
cause: error,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'RequestFailed': {
|
|
170
|
+
const code: ParaPasskeyErrorCode = 'REQUEST_FAILED';
|
|
171
|
+
return new ParaPasskeyError({
|
|
172
|
+
code,
|
|
173
|
+
message: messageField,
|
|
174
|
+
platform,
|
|
175
|
+
suggestion:
|
|
176
|
+
'The passkey request failed. On iOS, check associated domains and Team ID + Bundle ID registration. On Android, verify your SHA-256 fingerprint in the Developer Portal.',
|
|
177
|
+
docsUrl: docsUrlForCode(code),
|
|
178
|
+
cause: error,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
case 'Interrupted': {
|
|
183
|
+
const code: ParaPasskeyErrorCode = 'INTERRUPTED';
|
|
184
|
+
return new ParaPasskeyError({
|
|
185
|
+
code,
|
|
186
|
+
message: messageField,
|
|
187
|
+
platform,
|
|
188
|
+
suggestion: 'The passkey operation was interrupted and may be retried.',
|
|
189
|
+
docsUrl: docsUrlForCode(code),
|
|
190
|
+
cause: error,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
case 'InvalidChallenge': {
|
|
195
|
+
const code: ParaPasskeyErrorCode = 'BAD_CONFIGURATION';
|
|
196
|
+
return new ParaPasskeyError({
|
|
197
|
+
code,
|
|
198
|
+
message: messageField,
|
|
199
|
+
platform,
|
|
200
|
+
suggestion: 'The challenge provided to the passkey request was invalid. This is likely an SDK internal issue.',
|
|
201
|
+
docsUrl: docsUrlForCode(code),
|
|
202
|
+
cause: error,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case 'InvalidUser':
|
|
207
|
+
case 'InvalidUserId': {
|
|
208
|
+
const code: ParaPasskeyErrorCode = 'BAD_CONFIGURATION';
|
|
209
|
+
return new ParaPasskeyError({
|
|
210
|
+
code,
|
|
211
|
+
message: messageField,
|
|
212
|
+
platform,
|
|
213
|
+
suggestion: 'The user ID provided to the passkey request was invalid. This is likely an SDK internal issue.',
|
|
214
|
+
docsUrl: docsUrlForCode(code),
|
|
215
|
+
cause: error,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
case 'HandlerUndefined': {
|
|
220
|
+
const code: ParaPasskeyErrorCode = 'NOT_SUPPORTED';
|
|
221
|
+
return new ParaPasskeyError({
|
|
222
|
+
code,
|
|
223
|
+
message: messageField,
|
|
224
|
+
platform,
|
|
225
|
+
suggestion:
|
|
226
|
+
'The passkey handler is not available. This typically means the iOS version does not support passkeys (requires iOS 16+).',
|
|
227
|
+
docsUrl: docsUrlForCode(code),
|
|
228
|
+
cause: error,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
case 'Native error': {
|
|
233
|
+
const gpsMatch = /\[(\d+)\]/.exec(messageField);
|
|
234
|
+
if (gpsMatch && gpsMatch[1] === '50152') {
|
|
235
|
+
const code: ParaPasskeyErrorCode = 'RP_ID_MISMATCH';
|
|
236
|
+
return new ParaPasskeyError({
|
|
237
|
+
code,
|
|
238
|
+
message: messageField,
|
|
239
|
+
platform,
|
|
240
|
+
suggestion:
|
|
241
|
+
"Your app's signing certificate SHA-256 fingerprint doesn't match what's registered in the Developer Portal. Debug and release builds use different signing keys. Run 'keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android' to get your debug fingerprint, then register it in the Developer Portal.",
|
|
242
|
+
docsUrl: docsUrlForCode(code),
|
|
243
|
+
cause: error,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const code: ParaPasskeyErrorCode = 'UNKNOWN';
|
|
247
|
+
return new ParaPasskeyError({
|
|
248
|
+
code,
|
|
249
|
+
message: messageField,
|
|
250
|
+
platform,
|
|
251
|
+
suggestion:
|
|
252
|
+
"An unrecognized native passkey error occurred. Check the 'cause' property for the original error details.",
|
|
253
|
+
docsUrl: docsUrlForCode(code),
|
|
254
|
+
cause: error,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
case 'UnknownError':
|
|
259
|
+
case 'Unknown error': {
|
|
260
|
+
const code: ParaPasskeyErrorCode = 'UNKNOWN';
|
|
261
|
+
return new ParaPasskeyError({
|
|
262
|
+
code,
|
|
263
|
+
message: messageField,
|
|
264
|
+
platform,
|
|
265
|
+
suggestion: "An unknown passkey error occurred. Check the 'cause' property for details.",
|
|
266
|
+
docsUrl: docsUrlForCode(code),
|
|
267
|
+
cause: error,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
default: {
|
|
272
|
+
const code: ParaPasskeyErrorCode = 'UNKNOWN';
|
|
273
|
+
return new ParaPasskeyError({
|
|
274
|
+
code,
|
|
275
|
+
message: messageField,
|
|
276
|
+
platform,
|
|
277
|
+
suggestion: "An unexpected error occurred during the passkey operation. Check the 'cause' property for details.",
|
|
278
|
+
docsUrl: docsUrlForCode(code),
|
|
279
|
+
cause: error,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function logPasskeyDiagnostic(error: ParaPasskeyError, operation: string, relyingPartyId: string): void {
|
|
286
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
287
|
+
console.error(
|
|
288
|
+
`[Para] Passkey ${operation} failed\n` +
|
|
289
|
+
` Code: ${error.code}\n` +
|
|
290
|
+
` Platform: ${error.platform}\n` +
|
|
291
|
+
` Message: ${error.message}\n` +
|
|
292
|
+
` RP ID: ${relyingPartyId}\n` +
|
|
293
|
+
` Suggestion: ${error.suggestion}\n` +
|
|
294
|
+
` Docs: ${error.docsUrl}`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|