-
Notifications
You must be signed in to change notification settings - Fork 267
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
Fix bug in Mobile Safari 18.x: start poll request when document is visible and awaken #1552
base: master
Are you sure you want to change the base?
Conversation
lib/authn/util/poll.ts
Outdated
const isNetworkError = isAuthApiError(err) && err.message === 'Load failed'; | ||
const isTimeout = isAuthApiError(err) && err.message === 'Load timeout'; |
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.
Looks brittle. Check that this works cross-browser (at least doesn't cause regression)
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.
This check for Load failed
message is intended to work only in Safari
c8d0649
to
bd83de1
Compare
|
||
// Restarts fetch on 'Load failed' error | ||
// This error can occur when `fetch` does not respond | ||
// (due to CORS error, non-existing host, or network error) |
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.
Is there a way to differentiate between/among these types of errors?
Seems to me isNetworkError
will be true in all those cases and retry the fetch, although we don't want to retry on CORS, non-existent host, etc.
LMK if I'm misreading/misunderstanding the behavior
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.
- Google search results for "safari types of network error"
- Google search results for "chrome devtools types of network error"
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.
Probably good to add some tests to ensure we don't catch and retry CORS errors, etc
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.
You're correct, isNetworkError
will be true in all those 3 cases.
There is no way to distinguish programmatically in JS.
(The difference can be seen only in browser console)
That's why I added canRetry
option to enable retry logic only for polling requests.
Polling requests are repeatable by design so I think it's fine to retry it due to real network loss.
Since all API requests should be performed to the same host and poll request is never a first request in a list of ones performed by SIW, possible CORS error should be caught before polling is started.
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.
Thanks. Is there any logic to say we should stop trying polling after n failed attempts?
lib/http/request.ts
Outdated
return sdk.options.httpRequestClient!(method!, url!, ajaxOptions) | ||
var err, res, promise; | ||
|
||
if (isIOS()) { |
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.
My initial research/assumptions about browser version detection (I seemed to have confused it with OS version) were wrong. We can actually determine the browser version. This is a library that does the parsing: https://www.npmjs.com/package/ua-parser-js
I think it's fine to use it or use it as a reference. Either way we should gate this by the specific browser versions 18.x
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.
url: target, | ||
method: actionDefinition.method, | ||
headers, | ||
args: body, | ||
withCredentials: toPersist?.withCredentials ?? true | ||
}); | ||
}; | ||
const isPolling = target.endsWith('/poll'); |
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.
I'm not sure we can guarantee all IDX polling flows utilize paths that end in /poll
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.
changed to
const isPolling = actionDefinition.name === 'poll' || actionDefinition.name?.endsWith('-poll');
lib/http/request.ts
Outdated
@@ -113,7 +128,8 @@ export function httpRequest(sdk: OktaAuthHttpInterface, options: RequestOptions) | |||
withCredentials = options.withCredentials === true, // default value is false | |||
storageUtil = sdk.options.storageUtil, | |||
storage = storageUtil!.storage, | |||
httpCache = sdk.storageManager.getHttpCache(sdk.options.cookies); | |||
httpCache = sdk.storageManager.getHttpCache(sdk.options.cookies), | |||
canRetry = options.canRetry; |
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.
Can you add an additional option here to gate this logic? Something like pollingIntent: true/false
(defaults to false
). Then change L163 to
if (pollingIntent && isIOS() { ... }
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.
Both the poll function of authn
and the SIW polling methods would need to pass this flag
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.
Done
0f4536c
to
6f39061
Compare
if (isBrowser() && typeof navigator !== 'undefined' && typeof navigator.userAgent !== 'undefined') { | ||
// eslint-disable-next-line new-cap | ||
const { browser, os } = UAParser(navigator.userAgent); |
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.
Does UAParser
not have these kinds of safety checks? If you wrote it this way because to navigator.userAgent
"might be undefined", let UAParser find the user-agent string? The new
will also avoid the need for the eslint annotation
const { browser, os } = new UAParser().getResult();
I think your implementation/invocation is useful when you're running it in a node context or analyzing data.
|
||
// Restarts fetch on 'Load failed' error | ||
// This error can occur when `fetch` does not respond | ||
// (due to CORS error, non-existing host, or network error) |
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.
Thanks. Is there any logic to say we should stop trying polling after n failed attempts?
Fixes bug in Mobile Safari 18.0-18.2 for poll requests.
We should wait ~500ms after document become visible before making poll requests.
If
fetch
still failed with errorLoad failed
, restart fetch request.