-
Notifications
You must be signed in to change notification settings - Fork 927
Fix App Check timer issues #6617
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🦋 Changeset detectedLatest commit: 15a0524 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Size Report 1Affected Products
Test Logs |
Size Analysis Report 1Affected Products
Test Logs |
Size Analysis Report 1Affected Products
Test Logs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work!! Everything looks good to me here
Hi @hsubox76 Do you know if this fix has been published yet? I'm currently running into mega infinite loop App Check problems (we're getting 90K invalid requests constantly every single hour for the last 7 days) and I think this may go a long way to fix them. We're really keen to update and try this fix as soon as possible. Thanks for all the hard work |
Fixes #6373
Causes
Found 2 causes of the problems:
getToken()
needs to return an error result (a dummy token with anerror
property) whenever there is an error. It was assuming that if there was an error there would be no token. This is not always the case. If the token is about to expire, the proactive refresh attempt may run into an error at the exchange endpoint. Then there will be an existing valid token, but also en error.getToken()
makes a request to the exchange endpoint, it stores the promise instate.exchangeTokenPromise
and doesn't make another request if the promise is in flight, instead, any calls during that period await the existing promise. Once the promise is resolved, it clearsstate.exchangeTokenPromise
, allowing future exchange requests to be made when needed. The mistake was that it only cleared this when the promise was resolved, and not when the promise was rejected, so that once an exchange request promise failed, another one wouldn't be made until memory was cleared. (This may be why users had to refresh the page.)Solutions
getToken()
to return a token with a special error property,internalError
. The reason this needs to be different fromerror
, is that there's 2 different places that look for theerror
property on the result returned bygetToken()
. The first is the Refresheroperation
callback (defined insidecreateTokenRefresher
), where we need it to find theerror
property (indicating there was an exchange request error) and throw, which will cause the Refresher to go into backoff retry mode. However, the other place that looks aterror
isnotifyTokenListeners
which calls the error callback for any 3P listeners if it findserror
. If we have a valid token still, and it's just the proactive refresh attempt that's failing, we should just keep sending the token to 3P listeners as usual, it isn't an error from their point of view. That's why we useinternalError
for this case, sonotifyTokenListeners
can ignore it and the Refresher's operation callback can use it and throw, just as it does forerror
.then
withfinally
. Thereturn token
part of thethen
callback is no longer needed as the original promise already returns the token.Testing
Tested this using a 30 minute token and taking the tab offline using devtools, after initial token fetch. While offline, the app attempted to make requests to Firestore, and later App Check, after the App Check token had expired, but no more than 1 request per minute (and I think most were triggered by Firestore, which needs to get an App Check token when it tries to connect to the backend). After returning tab to online state, it immediately got a new token, and Firestore was able to write.