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(session): Safari bug with delayed set of cookies #2179

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
51 changes: 40 additions & 11 deletions src/background/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,47 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0
*/
import { UPDATE_SESSION_ACTION_NAME } from '/store/session.js';
import { HOME_PAGE_URL, ACCOUNT_PAGE_URL } from '/utils/api.js';
import { store } from 'hybrids';
import Session, { UPDATE_SESSION_ACTION_NAME } from '/store/session.js';
import { HOME_PAGE_URL, ACCOUNT_PAGE_URL, COOKIE_DOMAIN } from '/utils/api.js';

function refreshSession() {
chrome.runtime
.sendMessage({ action: UPDATE_SESSION_ACTION_NAME })
.catch(() => null);
}

// Observe cookie changes (login/logout actions)
chrome.webNavigation.onDOMContentLoaded.addListener(async ({ url = '' }) => {
if (url === HOME_PAGE_URL || url.includes(ACCOUNT_PAGE_URL)) {
// Send message to update session in other contexts
chrome.runtime
.sendMessage({ action: UPDATE_SESSION_ACTION_NAME })
// The function only throws if the other end does not exist. Mainly, it happens
// when the background process starts, but there is no other content script or
// extension page, which could receive a message.
.catch(() => null);
chrome.cookies.onChanged.addListener(async ({ cookie }) => {
if (cookie.domain === COOKIE_DOMAIN && cookie.name === 'access_token') {
refreshSession();
}
});

async function retry(fn, retries = 10, delay = 1000) {
if (!(await fn())) {
if (retries > 0) {
await new Promise((resolve) => setTimeout(resolve, delay));
return retry(fn, retries - 1, delay);
}
}
}

if (__PLATFORM__ === 'safari') {
chrome.webNavigation.onDOMContentLoaded.addListener(async ({ url = '' }) => {
// Safari on iOS 18.x has a bug where cookies are available to the extension with a significant delay
// However, there is no event to listen to when the cookies are available, we need to poll
// To avoid the same possible problem on macOS, we do it for all Safari platforms
// TODO: Check if this is still needed after the next Safari release

if (url === HOME_PAGE_URL) {
// Check for logged out state
await retry(() => store.resolve(Session).then(({ user }) => !user));
refreshSession();
} else if (url.includes(ACCOUNT_PAGE_URL)) {
// Check for logged in state
await retry(() => store.resolve(Session).then(({ user }) => user));
refreshSession();
}
});
}
7 changes: 3 additions & 4 deletions src/pages/settings/views/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,27 @@ function openGhosteryPage(url) {
details.tabId === tab.id &&
details.url.startsWith(ACCOUNT_PAGE_URL)
) {
chrome.webNavigation.onCommitted.removeListener(onSuccess);
chrome.webNavigation.onDOMContentLoaded.removeListener(onSuccess);
chrome.tabs.remove(tab.id);
}
};

const onRemove = (tabId) => {
if (tabId === tab.id) {
chrome.webNavigation.onCommitted.removeListener(onSuccess);
chrome.webNavigation.onDOMContentLoaded.removeListener(onSuccess);
chrome.tabs.onRemoved.removeListener(onRemove);

// The tab is closed before the background listeners can catch the event
// so we need to refresh session and trigger sync manually
store.clear(Session);
chrome.runtime.sendMessage({ action: 'syncOptions' });

// Restore the original tab
chrome.tabs.update(currentTab[0].id, { active: true });
}
};

chrome.webNavigation.onDOMContentLoaded.addListener(onSuccess);
chrome.tabs.onRemoved.addListener(onRemove);
chrome.webNavigation.onCommitted.addListener(onSuccess);

tab = await chrome.tabs.create({ url });
};
Expand Down
24 changes: 17 additions & 7 deletions src/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,23 @@ async function getCookie(name) {
: {}),
});

if (
cookie &&
(cookie.session ||
cookie.expirationDate * (__PLATFORM__ !== 'safari' ? 1000 : 1) >
Date.now())
) {
return cookie;
if (cookie) {
if (cookie.session) return cookie;

let expirationDate = cookie.expirationDate;

// By the specs, the `expirationDate` should be in seconds since the epoch
// and we need to convert it to milliseconds, but Safari returns it in milliseconds
if (
__PLATFORM__ !== 'safari' ||
new Date(expirationDate).getFullYear() === 1970
) {
expirationDate = expirationDate * 1000;
}

if (expirationDate > Date.now()) {
return cookie;
}
}

return null;
Expand Down
Loading