Skip to content

Commit

Permalink
fix(session): Safari bug with delayed set of cookies
Browse files Browse the repository at this point in the history
  • Loading branch information
smalluban committed Jan 10, 2025
1 parent 84ec1c5 commit de4a762
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 22 deletions.
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

0 comments on commit de4a762

Please sign in to comment.