diff --git a/services/web/frontend/js/features/user-content-domain-access-check/index.ts b/services/web/frontend/js/features/user-content-domain-access-check/index.ts index 1496578506..c1de5b66f0 100644 --- a/services/web/frontend/js/features/user-content-domain-access-check/index.ts +++ b/services/web/frontend/js/features/user-content-domain-access-check/index.ts @@ -10,6 +10,7 @@ import { captureException } from '../../infrastructure/error-reporter' import { postJSON } from '../../infrastructure/fetch-json' import isSplitTestEnabled from '../../utils/isSplitTestEnabled' +const MAX_CHECKS_PER_PAGE_LOAD = 3 const INITIAL_DELAY_MS = 30_000 const DELAY_BETWEEN_PROBES_MS = 1_000 const TIMEOUT_MS = 30_000 @@ -180,12 +181,18 @@ export async function checkUserContentDomainAccess() { } let failed = 0 + const epochBeforeCheck = networkEpoch for (const { url, init, estimatedSize, hash, chunks } of cases) { await sleep(DELAY_BETWEEN_PROBES_MS) try { await singleCheck(url, init, estimatedSize, hash, chunks) } catch (err: any) { + if (!navigator.onLine || epochBeforeCheck !== networkEpoch) { + // It is very likely that the request failed because we are offline or + // the network connection changed just now. + return false + } failed++ OError.tag(err, 'user-content-domain-access-check failed', { url, @@ -214,8 +221,45 @@ export function userContentDomainAccessCheckPassed() { return accessCheckPassed } +let networkEpoch = performance.now() +window.addEventListener('offline', () => { + // We are offline. Abort any scheduled check. + clearTimeout(lastScheduledCheck) + accessCheckPassed = false + networkEpoch = performance.now() +}) +window.addEventListener('online', () => { + // We are online again. Schedule another check for this network. + accessCheckPassed = false + networkEpoch = performance.now() + scheduleUserContentDomainAccessCheck() +}) +try { + // Note: navigator.connection is not available on Firefox and Safari. + // Docs: https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation + navigator.connection.addEventListener('change', () => { + // The network changed. Schedule another check for it. + accessCheckPassed = false + networkEpoch = performance.now() + scheduleUserContentDomainAccessCheck() + }) +} catch (e) {} + +let lastScheduledCheck: number +let remainingChecks = MAX_CHECKS_PER_PAGE_LOAD export function scheduleUserContentDomainAccessCheck() { - sleep(INITIAL_DELAY_MS).then(() => { + if (!isSplitTestEnabled('user-content-domain-access-check')) return + clearTimeout(lastScheduledCheck) + const networkEpochBeforeDelay = networkEpoch + lastScheduledCheck = window.setTimeout(() => { + if (!window.navigator.onLine || networkEpochBeforeDelay !== networkEpoch) { + // Must be online for more than INITIAL_DELAY_MS before we check. + // We want to avoid false-positives from flaky network connections. + // Try again in INITIAL_DELAY_MS. + return scheduleUserContentDomainAccessCheck() + } + if (accessCheckPassed) return + if (remainingChecks-- <= 0) return checkUserContentDomainAccess() .then(ok => { accessCheckPassed = ok @@ -223,5 +267,5 @@ export function scheduleUserContentDomainAccessCheck() { .catch(err => { captureException(err) }) - }) + }, INITIAL_DELAY_MS) } diff --git a/services/web/frontend/js/ide.js b/services/web/frontend/js/ide.js index a5ec888fde..e8da4fe218 100644 --- a/services/web/frontend/js/ide.js +++ b/services/web/frontend/js/ide.js @@ -481,9 +481,7 @@ If the project has been renamed please look in your project list for a new proje ) cleanupServiceWorker() -if (isSplitTestEnabled('user-content-domain-access-check')) { - scheduleUserContentDomainAccessCheck() -} +scheduleUserContentDomainAccessCheck() angular.module('SharelatexApp').config(function ($provide) { $provide.decorator('$browser', [