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

Do not use AbortController in the worker if not available #679

Merged
merged 7 commits into from
Jan 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 3 additions & 20 deletions __tests__/Auth0Client/getTokenSilently.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,34 +619,17 @@ describe('Auth0Client', () => {
supported: false
},
{
name: 'Safari 10',
name: 'Chrome',
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8',
supported: false
},
{
name: 'Safari 11',
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.1.28 (KHTML, like Gecko) Version/11.0 Safari/604.1.28',
supported: false
},
{
name: 'Safari 12',
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.1 Safari/605.1.15',
supported: false
},
{
name: 'Safari 12.1',
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
supported: true
}
].forEach(({ name, userAgent, supported }) =>
it(`refreshes the token ${
supported ? 'with' : 'without'
} the worker, when ${name}`, async () => {
const originalUserAgent = window.navigator.userAgent;

Object.defineProperty(window.navigator, 'userAgent', {
value: userAgent,
configurable: true
Expand Down
3 changes: 2 additions & 1 deletion __tests__/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ describe('oauthToken', () => {
'Content-type': 'application/json',
'Auth0-Client': btoa(JSON.stringify(auth0Client))
},
method: 'POST'
method: 'POST',
signal: new AbortController().signal
},
auth: {
audience: '__test_audience__',
Expand Down
85 changes: 85 additions & 0 deletions __tests__/token.worker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,33 @@ describe('token worker', () => {
json: () => ({ foo: 'bar', refresh_token: 'baz' })
})
);

const response = await messageHandlerAsync({
fetchUrl: '/foo',
fetchOptions: {
method: 'POST',
body: JSON.stringify({})
}
});

expect(response.json).toEqual({
foo: 'bar'
});

expect(mockFetch.mock.calls[0][1].signal).toBeDefined();
});

it('calls fetch without AbortSignal if AbortController is not available', async () => {
const originalAbortController = window.AbortController;
delete window.AbortController;

mockFetch.mockReturnValue(
Promise.resolve({
ok: true,
json: () => ({ foo: 'bar', refresh_token: 'baz' })
})
);

const response = await messageHandlerAsync({
fetchUrl: '/foo',
fetchOptions: {
Expand All @@ -47,6 +74,10 @@ describe('token worker', () => {
expect(response.json).toEqual({
foo: 'bar'
});

expect(mockFetch.mock.calls[0][1].signal).toBeUndefined();

window.AbortController = originalAbortController;
});

it(`stores the refresh token and uses it for grant_type='refresh_token'`, async () => {
Expand Down Expand Up @@ -100,16 +131,70 @@ describe('token worker', () => {

it(`errors when fetch rejects`, async () => {
mockFetch.mockReturnValue(Promise.reject(new Error('fail')));

const response = await messageHandlerAsync({
fetchUrl: '/foo',
fetchOptions: {
method: 'POST',
body: JSON.stringify({})
}
});

expect(response.error).toEqual('fail');
});

it(`aborts when timed out`, async () => {
const originalAbortController = window.AbortController;
const abortFn = jest.fn();

window.AbortController = jest.fn(() => ({
signal: {},
abort: abortFn
})) as any;

mockFetch.mockReturnValue(
new Promise(resolve => {
setTimeout(resolve, 1000);
})
);

const response = await messageHandlerAsync({
fetchUrl: '/foo',
fetchOptions: {
method: 'POST',
body: JSON.stringify({})
},
timeout: 1
});

expect(response.error).toEqual("Timeout when executing 'fetch'");
expect(abortFn).toHaveBeenCalled();
window.AbortController = originalAbortController;
});

it(`does not abort when timed out if no abort controller`, async () => {
const originalAbortController = window.AbortController;
delete window.AbortController;

mockFetch.mockReturnValue(
new Promise(resolve => {
setTimeout(resolve, 1000);
})
);

const response = await messageHandlerAsync({
fetchUrl: '/foo',
fetchOptions: {
method: 'POST',
body: JSON.stringify({})
},
timeout: 1
});

expect(response.error).toEqual("Timeout when executing 'fetch'");
window.AbortController = originalAbortController;
});

it('removes the stored refresh token if none was returned from the server', async () => {
mockFetch
.mockReturnValueOnce(
Expand Down
5 changes: 2 additions & 3 deletions src/Auth0Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import {

// @ts-ignore
import TokenWorker from './worker/token.worker.ts';
import { isIE11, isSafari10, isSafari11, isSafari12_0 } from './user-agent';
import { isIE11 } from './user-agent';
import { singlePromise, retryPromise } from './promise-utils';

/**
Expand Down Expand Up @@ -92,8 +92,7 @@ const cacheFactory = (location: string) => {
/**
* @ignore
*/
const supportWebWorker = () =>
!isIE11() && !isSafari10() && !isSafari11() && !isSafari12_0();
const supportWebWorker = () => !isIE11();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@frederikprijck is it fair to say we only added these Safari checks to stop it from using the web worker purely because of AbortConrtoller, or were there other factors?

I haven't removed these functions, do you see value in keeping them around or should we remove them?

Copy link
Member

Choose a reason for hiding this comment

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

It was to fix the AbortController: #594

Copy link
Member

Choose a reason for hiding this comment

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

If we revert this, we should test this again on Safari. LMK if you dont have access to BrowserStack, I dont mind testing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I've tested, versions I tested are in the PR


/**
* @ignore
Expand Down
9 changes: 0 additions & 9 deletions src/user-agent.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1 @@
export const isIE11 = () => /Trident.*rv:11\.0/.test(navigator.userAgent);

export const isSafari10 = () =>
/AppleWebKit.*Version\/10/.test(navigator.userAgent);

export const isSafari11 = () =>
/AppleWebKit.*Version\/11/.test(navigator.userAgent);

export const isSafari12_0 = () =>
/AppleWebKit.*Version\/12\.0/.test(navigator.userAgent);
17 changes: 13 additions & 4 deletions src/worker/token.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,19 @@ const messageHandler = async ({
});
}

const abortController = new AbortController();
const { signal } = abortController;
let abortController: AbortController;

if (typeof AbortController === 'function') {
abortController = new AbortController();
fetchOptions.signal = abortController.signal;
}

let response: any;

try {
response = await Promise.race([
wait(timeout),
fetch(fetchUrl, { ...fetchOptions, signal })
fetch(fetchUrl, { ...fetchOptions })
]);
} catch (error) {
// fetch error, reject `sendMessage` using `error` key so that we retry.
Expand All @@ -67,7 +71,12 @@ const messageHandler = async ({

if (!response) {
// If the request times out, abort it and let `fetchWithTimeout` raise the error.
abortController.abort();
if (abortController) abortController.abort();

port.postMessage({
error: "Timeout when executing 'fetch'"
});
Copy link
Member

Choose a reason for hiding this comment

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

Why are we adding this?

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 was mostly for the benefit of the test, to be able to determine that the failure was because of a timeout, but could also be a useful error message on the client regardless. What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

I think this is fine. It rejects the promise using the same error we already have incase it timesout based on the setTimeout.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It shouldn't do.. if the timeout is hit, no response will be returned from Promise.race and falls into the next block that checks for an empty response, rather than falling into the catch. I could be mistaken; I tried to test for that here: https://github.com/auth0/auth0-spa-js/blob/fix/abort-controller/__tests__/token.worker.test.ts#L146


return;
}

Expand Down