@getpara/react-native-wallet 2.22.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 CHANGED
@@ -1,5 +1,9 @@
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';
7
+ import type { ParaMobile as _ParaMobile } from './react-native/ParaMobile.js';
8
+ /** Hook for retrieving the Para client from the provider context. */
9
+ export declare const useClient: () => _ParaMobile | undefined;
package/dist/index.js CHANGED
@@ -1,6 +1,10 @@
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
6
7
  export * from '@getpara/react-core';
8
+ import { useClient as _useClient } from '@getpara/react-core';
9
+ /** Hook for retrieving the Para client from the provider context. */
10
+ export const useClient = () => _useClient();
@@ -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
- const result = yield Passkey.create(requestJson);
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
- const result = yield Passkey.get(requestJson);
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.22.0",
4
+ "version": "2.24.0",
5
5
  "author": "Para Team <hello@getpara.com> (https://getpara.com)",
6
6
  "dependencies": {
7
- "@getpara/core-sdk": "2.22.0",
8
- "@getpara/react-core": "2.22.0",
9
- "@getpara/user-management-client": "2.22.0",
10
- "@getpara/viem-v2-integration": "2.22.0",
11
- "@getpara/web-sdk": "2.22.0",
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": "7191b8c823ec592eb1da16c9a085f8e69c6bf377"
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';
@@ -9,3 +10,8 @@ export * from './provider/index.js';
9
10
 
10
11
  // Core provider hooks and config types — platform-agnostic, work with any ParaCore subclass
11
12
  export * from '@getpara/react-core';
13
+
14
+ import { useClient as _useClient } from '@getpara/react-core';
15
+ import type { ParaMobile as _ParaMobile } from './react-native/ParaMobile.js';
16
+ /** Hook for retrieving the Para client from the provider context. */
17
+ export const useClient = () => _useClient<_ParaMobile>();
@@ -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
- const result: PasskeyCreateResult = await Passkey.create(requestJson);
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
- const result: PasskeyGetResult = await Passkey.get(requestJson);
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
+ }