@@ -2,9 +2,113 @@ import { captureException } from "@sentry/react-native";
22
33export default function reportError ( error : unknown , hint ?: Parameters < typeof captureException > [ 1 ] ) {
44 console . error ( error ) ; // eslint-disable-line no-console
5+ const classification = classify ( parseError ( error ) ) ;
6+ if ( classification . expected ) return ;
57 try {
6- return captureException ( error , hint ) ;
8+ if ( hint ) return captureException ( error , hint ) ;
9+ return captureException (
10+ error ,
11+ classification . fingerprint ? { fingerprint : classification . fingerprint } : undefined ,
12+ ) ;
713 } catch ( sentryError ) {
814 console . error ( sentryError ) ; // eslint-disable-line no-console
915 }
1016}
17+
18+ const passkeyCancelledMessages = new Set ( [
19+ "The operation couldn’t be completed. (com.apple.AuthenticationServices.AuthorizationError error 1001.)" ,
20+ "The operation couldn’t be completed. (com.apple.AuthenticationServices.AuthorizationError error 1004.)" ,
21+ "The operation couldn’t be completed. Device must be unlocked to perform request." ,
22+ "UserCancelled" ,
23+ ] ) ;
24+ const passkeyExpectedMessages = new Set ( [
25+ ...passkeyCancelledMessages ,
26+ "The operation couldn’t be completed. Stolen Device Protection is enabled and biometry is required." ,
27+ ] ) ;
28+ const authPrefixes = [ "androidx.credentials.exceptions.domerrors.NotAllowedError" ] ;
29+ type ParsedError = ReturnType < typeof parseError > ;
30+
31+ export function isPasskeyExpected ( error : unknown ) {
32+ return classify ( parseError ( error ) ) . passkeyExpected ;
33+ }
34+
35+ export function isPasskeyCancelled ( error : unknown ) {
36+ return classify ( parseError ( error ) ) . passkeyCancelled ;
37+ }
38+
39+ export function isAuthExpected ( error : unknown ) {
40+ return classify ( parseError ( error ) ) . authExpected ;
41+ }
42+
43+ export function isExpected ( error : unknown ) {
44+ return classify ( parseError ( error ) ) . expected ;
45+ }
46+
47+ export function fingerprint ( error : unknown ) {
48+ return classify ( parseError ( error ) ) . fingerprint ;
49+ }
50+
51+ export function classifyError ( error : unknown ) {
52+ return classify ( parseError ( error ) ) ;
53+ }
54+
55+ function parseError ( error : unknown ) {
56+ const code =
57+ typeof error === "object" &&
58+ error !== null &&
59+ "code" in error &&
60+ typeof error . code === "string" &&
61+ error . code . length > 0
62+ ? error . code
63+ : undefined ;
64+ const name =
65+ typeof error === "object" &&
66+ error !== null &&
67+ "name" in error &&
68+ typeof error . name === "string" &&
69+ error . name . length > 0
70+ ? error . name
71+ : undefined ;
72+ const message =
73+ error instanceof Error
74+ ? normalizeMessage ( error . message )
75+ : typeof error === "string"
76+ ? normalizeMessage ( error )
77+ : typeof error === "object" &&
78+ error !== null &&
79+ "message" in error &&
80+ typeof error . message === "string" &&
81+ error . message . length > 0
82+ ? normalizeMessage ( error . message )
83+ : undefined ;
84+ return { code, name, message } ;
85+ }
86+
87+ function classify ( { code, message } : ParsedError ) {
88+ const passkeyCancelled = message !== undefined && passkeyCancelledMessages . has ( message ) ;
89+ const passkeyExpected =
90+ passkeyCancelled ||
91+ ( message !== undefined &&
92+ ( passkeyExpectedMessages . has ( message ) ||
93+ message . includes ( "There is already a pending passkey request" ) ||
94+ authPrefixes . some ( ( prefix ) => message . startsWith ( prefix ) ) ) ) ;
95+ const authExpected = passkeyExpected || message === "invalid operation" ;
96+ const expected = authExpected ;
97+ const fingerprintMessage =
98+ message ?. startsWith ( "Calling the 'get' function has failed" ) ||
99+ message ?. startsWith ( "The operation couldn’t be completed." )
100+ ? message
101+ : undefined ;
102+ const value =
103+ code !== undefined && code !== "ERR_UNKNOWN"
104+ ? [ "{{ default }}" , code ]
105+ : fingerprintMessage === undefined
106+ ? undefined
107+ : [ "{{ default }}" , fingerprintMessage ] ;
108+ return { passkeyExpected, passkeyCancelled, authExpected, expected, fingerprint : value } ;
109+ }
110+
111+ function normalizeMessage ( message : string ) {
112+ const value = message . startsWith ( "Error: " ) ? message . slice ( "Error: " . length ) : message ;
113+ return value . trim ( ) ;
114+ }
0 commit comments