@@ -3,7 +3,7 @@ import {startAuthentication, startRegistration} from "@simplewebauthn/browser";
3
3
import { PasskeyApiClient } from "./api" ;
4
4
import { AuthenticationResponseJSON , RegistrationResponseJSON , AuthenticatorAttachment } from "@simplewebauthn/types" ;
5
5
import { TokenCache } from "./token-cache" ;
6
- import { handleErrorResponse } from "./helpers" ;
6
+ import { handleErrorResponse , handleWebAuthnError } from "./helpers" ;
7
7
import { AuthsignalResponse } from "./types" ;
8
8
9
9
type PasskeyOptions = {
@@ -43,6 +43,8 @@ type SignInResponse = {
43
43
authenticationResponse ?: AuthenticationResponseJSON ;
44
44
} ;
45
45
46
+ let autofillRequestPending = false ;
47
+
46
48
export class Passkey {
47
49
public api : PasskeyApiClient ;
48
50
private passkeyLocalStorageKey = "as_user_passkey_map" ;
@@ -80,35 +82,43 @@ export class Passkey {
80
82
return handleErrorResponse ( optionsResponse ) ;
81
83
}
82
84
83
- const registrationResponse = await startRegistration ( { optionsJSON : optionsResponse . options , useAutoRegister} ) ;
85
+ try {
86
+ const registrationResponse = await startRegistration ( { optionsJSON : optionsResponse . options , useAutoRegister} ) ;
84
87
85
- const addAuthenticatorResponse = await this . api . addAuthenticator ( {
86
- challengeId : optionsResponse . challengeId ,
87
- registrationCredential : registrationResponse ,
88
- token : userToken ,
89
- } ) ;
88
+ const addAuthenticatorResponse = await this . api . addAuthenticator ( {
89
+ challengeId : optionsResponse . challengeId ,
90
+ registrationCredential : registrationResponse ,
91
+ token : userToken ,
92
+ } ) ;
90
93
91
- if ( "error" in addAuthenticatorResponse ) {
92
- return handleErrorResponse ( addAuthenticatorResponse ) ;
93
- }
94
+ if ( "error" in addAuthenticatorResponse ) {
95
+ return handleErrorResponse ( addAuthenticatorResponse ) ;
96
+ }
94
97
95
- if ( addAuthenticatorResponse . isVerified ) {
96
- this . storeCredentialAgainstDevice ( {
97
- ...registrationResponse ,
98
- userId : addAuthenticatorResponse . userId ,
99
- } ) ;
100
- }
98
+ if ( addAuthenticatorResponse . isVerified ) {
99
+ this . storeCredentialAgainstDevice ( {
100
+ ...registrationResponse ,
101
+ userId : addAuthenticatorResponse . userId ,
102
+ } ) ;
103
+ }
101
104
102
- if ( addAuthenticatorResponse . accessToken ) {
103
- this . cache . token = addAuthenticatorResponse . accessToken ;
104
- }
105
+ if ( addAuthenticatorResponse . accessToken ) {
106
+ this . cache . token = addAuthenticatorResponse . accessToken ;
107
+ }
105
108
106
- return {
107
- data : {
108
- token : addAuthenticatorResponse . accessToken ,
109
- registrationResponse,
110
- } ,
111
- } ;
109
+ return {
110
+ data : {
111
+ token : addAuthenticatorResponse . accessToken ,
112
+ registrationResponse,
113
+ } ,
114
+ } ;
115
+ } catch ( e ) {
116
+ autofillRequestPending = false ;
117
+
118
+ handleWebAuthnError ( e ) ;
119
+
120
+ throw e ;
121
+ }
112
122
}
113
123
114
124
async signIn ( params ?: SignInParams ) : Promise < AuthsignalResponse < SignInResponse > > {
@@ -120,9 +130,19 @@ export class Passkey {
120
130
throw new Error ( "action is not supported when providing a token" ) ;
121
131
}
122
132
133
+ if ( params ?. autofill ) {
134
+ if ( autofillRequestPending ) {
135
+ return { } ;
136
+ } else {
137
+ autofillRequestPending = true ;
138
+ }
139
+ }
140
+
123
141
const challengeResponse = params ?. action ? await this . api . challenge ( params . action ) : null ;
124
142
125
143
if ( challengeResponse && "error" in challengeResponse ) {
144
+ autofillRequestPending = false ;
145
+
126
146
return handleErrorResponse ( challengeResponse ) ;
127
147
}
128
148
@@ -132,50 +152,64 @@ export class Passkey {
132
152
} ) ;
133
153
134
154
if ( "error" in optionsResponse ) {
155
+ autofillRequestPending = false ;
156
+
135
157
return handleErrorResponse ( optionsResponse ) ;
136
158
}
137
159
138
- const authenticationResponse = await startAuthentication ( {
139
- optionsJSON : optionsResponse . options ,
140
- useBrowserAutofill : params ?. autofill ,
141
- } ) ;
160
+ try {
161
+ const authenticationResponse = await startAuthentication ( {
162
+ optionsJSON : optionsResponse . options ,
163
+ useBrowserAutofill : params ?. autofill ,
164
+ } ) ;
142
165
143
- if ( params ?. onVerificationStarted ) {
144
- params . onVerificationStarted ( ) ;
145
- }
166
+ if ( params ?. onVerificationStarted ) {
167
+ params . onVerificationStarted ( ) ;
168
+ }
146
169
147
- const verifyResponse = await this . api . verify ( {
148
- challengeId : optionsResponse . challengeId ,
149
- authenticationCredential : authenticationResponse ,
150
- token : params ?. token ,
151
- deviceId : this . anonymousId ,
152
- } ) ;
170
+ const verifyResponse = await this . api . verify ( {
171
+ challengeId : optionsResponse . challengeId ,
172
+ authenticationCredential : authenticationResponse ,
173
+ token : params ?. token ,
174
+ deviceId : this . anonymousId ,
175
+ } ) ;
153
176
154
- if ( "error" in verifyResponse ) {
155
- return handleErrorResponse ( verifyResponse ) ;
156
- }
177
+ if ( "error" in verifyResponse ) {
178
+ autofillRequestPending = false ;
157
179
158
- if ( verifyResponse . isVerified ) {
159
- this . storeCredentialAgainstDevice ( { ...authenticationResponse , userId : verifyResponse . userId } ) ;
160
- }
180
+ return handleErrorResponse ( verifyResponse ) ;
181
+ }
161
182
162
- if ( verifyResponse . accessToken ) {
163
- this . cache . token = verifyResponse . accessToken ;
164
- }
183
+ if ( verifyResponse . isVerified ) {
184
+ this . storeCredentialAgainstDevice ( { ... authenticationResponse , userId : verifyResponse . userId } ) ;
185
+ }
165
186
166
- const { accessToken : token , userId, userAuthenticatorId, username, userDisplayName, isVerified} = verifyResponse ;
167
-
168
- return {
169
- data : {
170
- isVerified,
171
- token,
172
- userId,
173
- userAuthenticatorId,
174
- username,
175
- displayName : userDisplayName ,
176
- authenticationResponse,
177
- } ,
178
- } ;
187
+ if ( verifyResponse . accessToken ) {
188
+ this . cache . token = verifyResponse . accessToken ;
189
+ }
190
+
191
+ const { accessToken : token , userId, userAuthenticatorId, username, userDisplayName, isVerified} = verifyResponse ;
192
+
193
+ autofillRequestPending = false ;
194
+
195
+ return {
196
+ data : {
197
+ isVerified,
198
+ token,
199
+ userId,
200
+ userAuthenticatorId,
201
+ username,
202
+ displayName : userDisplayName ,
203
+ authenticationResponse,
204
+ } ,
205
+ } ;
206
+ } catch ( e ) {
207
+ autofillRequestPending = false ;
208
+
209
+ handleWebAuthnError ( e ) ;
210
+
211
+ throw e ;
212
+ }
179
213
}
180
214
181
215
async isAvailableOnDevice ( { userId} : { userId : string } ) {
0 commit comments