Skip to content
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

Open
wants to merge 25 commits into
base: master
Choose a base branch
from

Conversation

denysoblohin-okta
Copy link
Contributor

@denysoblohin-okta denysoblohin-okta commented Dec 13, 2024

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 error Load failed, restart fetch request.

Comment on lines 198 to 199
const isNetworkError = isAuthApiError(err) && err.message === 'Load failed';
const isTimeout = isAuthApiError(err) && err.message === 'Load timeout';
Copy link
Contributor

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)

Copy link
Contributor Author

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

@denysoblohin-okta denysoblohin-okta force-pushed the od-fix-polling-in-bg branch 2 times, most recently from c8d0649 to bd83de1 Compare December 19, 2024 15:09
lib/constants.ts Show resolved Hide resolved

// 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)
Copy link
Contributor

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

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

Copy link
Contributor Author

@denysoblohin-okta denysoblohin-okta Dec 20, 2024

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.

Copy link
Contributor

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?

return sdk.options.httpRequestClient!(method!, url!, ajaxOptions)
var err, res, promise;

if (isIOS()) {
Copy link
Contributor

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

Copy link
Contributor Author

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');
Copy link
Contributor

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

Copy link
Contributor Author

@denysoblohin-okta denysoblohin-okta Dec 20, 2024

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');

https://github.com/okta/okta-signin-widget/blob/2cdaecd7f607f16875223652550cf603e476884b/src/v3/src/hooks/usePolling.ts#L36

@@ -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;
Copy link
Contributor

@jaredperreault-okta jaredperreault-okta Dec 19, 2024

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() { ... }

Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@denysoblohin-okta denysoblohin-okta changed the title Fix: restart poll requests that fail to fetch in background on iOS Fix bug in Mobile Safari 18.x: start poll request when document is visible and awaken Dec 20, 2024
Comment on lines +101 to +103
if (isBrowser() && typeof navigator !== 'undefined' && typeof navigator.userAgent !== 'undefined') {
// eslint-disable-next-line new-cap
const { browser, os } = UAParser(navigator.userAgent);
Copy link
Contributor

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)
Copy link
Contributor

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants