diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index 897ca5c14f..c5fd9b64b2 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -58,33 +58,16 @@ const getSplitTestOptions = callbackify(async function (req, res) { res, 'pdf-download-domain' ) - const { variant: forceNewDomainVariant } = - await SplitTestHandler.promises.getAssignment( - editorReq, - res, - 'force-new-compile-domain' - ) const pdfDownloadDomain = - (domainVariant === 'user' || forceNewDomainVariant === 'enabled') && - Settings.compilesUserContentDomain + domainVariant === 'user' && Settings.compilesUserContentDomain ? Settings.compilesUserContentDomain : Settings.pdfDownloadDomain - const { variant: hybridDomainVariant } = - await SplitTestHandler.promises.getAssignment( - editorReq, - res, - 'pdf-download-domain-hybrid' - ) - const enableHybridPdfDownload = hybridDomainVariant === 'enabled' - if (!req.query.enable_pdf_caching) { // The frontend does not want to do pdf caching. return { pdfDownloadDomain, - enableHybridPdfDownload, enablePdfCaching: false, - forceNewDomainVariant, } } @@ -101,18 +84,14 @@ const getSplitTestOptions = callbackify(async function (req, res) { // Skip the lookup of the chunk size when caching is not enabled. return { pdfDownloadDomain, - enableHybridPdfDownload, enablePdfCaching: false, - forceNewDomainVariant, } } const pdfCachingMinChunkSize = await getPdfCachingMinChunkSize(editorReq, res) return { pdfDownloadDomain, - enableHybridPdfDownload, enablePdfCaching, pdfCachingMinChunkSize, - forceNewDomainVariant, } }) @@ -154,13 +133,8 @@ module.exports = CompileController = { getSplitTestOptions(req, res, (err, splitTestOptions) => { if (err) return next(err) - let { - enablePdfCaching, - pdfCachingMinChunkSize, - pdfDownloadDomain, - enableHybridPdfDownload, - forceNewDomainVariant, - } = splitTestOptions + let { enablePdfCaching, pdfCachingMinChunkSize, pdfDownloadDomain } = + splitTestOptions options.enablePdfCaching = enablePdfCaching if (enablePdfCaching) { options.pdfCachingMinChunkSize = pdfCachingMinChunkSize @@ -227,8 +201,6 @@ module.exports = CompileController = { timings, pdfDownloadDomain, pdfCachingMinChunkSize, - enableHybridPdfDownload, - forceNewDomainVariant, }) } ) diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index ccaa065850..e015d47871 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -713,72 +713,6 @@ const ProjectController = { } ) }, - accessCheckForOldCompileDomainAssigment(cb) { - SplitTestHandler.getAssignment( - req, - res, - 'access-check-for-old-compile-domain', - () => { - // We'll pick up the assignment from the res.locals assignment. - cb() - } - ) - }, - forceNewDomainAssignment(cb) { - SplitTestHandler.getAssignment( - req, - res, - 'force-new-compile-domain', - () => { - // We'll pick up the assignment from the res.locals assignment. - cb() - } - ) - }, - userContentDomainAccessCheckAssigment(cb) { - SplitTestHandler.getAssignment( - req, - res, - 'user-content-domain-access-check', - () => { - // We'll pick up the assignment from the res.locals assignment. - cb() - } - ) - }, - userContentDomainAccessCheckDelayAssigment(cb) { - SplitTestHandler.getAssignment( - req, - res, - 'user-content-domain-access-check-delay', - () => { - // We'll pick up the assignment from the res.locals assignment. - cb() - } - ) - }, - userContentDomainAccessCheckMaxChecksAssigment(cb) { - SplitTestHandler.getAssignment( - req, - res, - 'user-content-domain-access-check-max-checks', - () => { - // We'll pick up the assignment from the res.locals assignment. - cb() - } - ) - }, - reportUserContentDomainAccessCheckErrorAssigment(cb) { - SplitTestHandler.getAssignment( - req, - res, - 'report-user-content-domain-access-check-error', - () => { - // We'll pick up the assignment from the res.locals assignment. - cb() - } - ) - }, }, ( err, diff --git a/services/web/app/src/Features/UserContentDomainCheck/UserContentDomainController.js b/services/web/app/src/Features/UserContentDomainCheck/UserContentDomainController.js deleted file mode 100644 index 06cc7223c6..0000000000 --- a/services/web/app/src/Features/UserContentDomainCheck/UserContentDomainController.js +++ /dev/null @@ -1,30 +0,0 @@ -const Metrics = require('@overleaf/metrics') - -function recordCheckResult(req, res) { - const path = req.body.isOldDomain ? 'old' : '' - Metrics.count('user_content_domain_check', req.body.succeeded, 1, { - status: 'success', - path, - }) - Metrics.count('user_content_domain_check', req.body.failed, 1, { - status: 'failure', - path, - }) - res.sendStatus(204) -} - -function recordFallbackUsage(_req, res) { - Metrics.inc('user_content_domain_fallback') - res.sendStatus(204) -} - -function recordMaxAccessChecksHit(_req, res) { - Metrics.inc('user_content_domain_max_access_checks_hit') - res.sendStatus(204) -} - -module.exports = { - recordCheckResult, - recordFallbackUsage, - recordMaxAccessChecksHit, -} diff --git a/services/web/app/src/router.js b/services/web/app/src/router.js index 082cd9ae85..fc50d138b5 100644 --- a/services/web/app/src/router.js +++ b/services/web/app/src/router.js @@ -67,7 +67,6 @@ const logger = require('@overleaf/logger') const _ = require('underscore') const { plainTextResponse } = require('./infrastructure/Response') const PublicAccessLevels = require('./Features/Authorization/PublicAccessLevels') -const UserContentDomainController = require('./Features/UserContentDomainCheck/UserContentDomainController') const rateLimiters = { addEmail: new RateLimiter('add-email', { @@ -201,21 +200,6 @@ const rateLimiters = { points: 10, duration: 60, }), - userContentDomainAccessCheckResult: new RateLimiter( - 'user-content-domain-a-c-r', - { - points: 30, - duration: 60, - } - ), - userContentDomainFallbackUsage: new RateLimiter('user-content-fb-u', { - points: 15, - duration: 60, - }), - userContentDomainMaxAccessChecksHit: new RateLimiter('user-content-mach', { - points: 15, - duration: 60, - }), } function initialize(webRouter, privateApiRouter, publicApiRouter) { @@ -1333,35 +1317,6 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) { res.sendStatus(204) }) - webRouter.post( - '/record-user-content-domain-access-check-result', - validate({ - body: Joi.object({ - failed: Joi.number().min(0).max(6), - succeeded: Joi.number().min(0).max(6), - isOldDomain: Joi.boolean().default(false), - }), - }), - RateLimiterMiddleware.rateLimit( - rateLimiters.userContentDomainAccessCheckResult - ), - UserContentDomainController.recordCheckResult - ) - webRouter.post( - '/record-user-content-domain-fallback-usage', - RateLimiterMiddleware.rateLimit( - rateLimiters.userContentDomainFallbackUsage - ), - UserContentDomainController.recordFallbackUsage - ) - webRouter.post( - '/record-user-content-domain-max-access-checks-hit', - RateLimiterMiddleware.rateLimit( - rateLimiters.userContentDomainMaxAccessChecksHit - ), - UserContentDomainController.recordMaxAccessChecksHit - ) - webRouter.get( `/read/:token(${TokenAccessController.READ_ONLY_TOKEN_PATTERN})`, RateLimiterMiddleware.rateLimit(rateLimiters.readOnlyToken), diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index abcd898b29..282692528d 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -609,7 +609,7 @@ "need_to_leave": "", "need_to_upgrade_for_more_collabs": "", "need_to_upgrade_for_more_collabs_variant": "", - "new_compile_domain_trouble_shooting": "", + "new_compile_domain_notice": "", "new_file": "", "new_folder": "", "new_name": "", diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.js b/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.js index c33aa3ca31..a25a72d8c9 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.js +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.js @@ -11,10 +11,6 @@ import PdfPreviewErrorBoundaryFallback from './pdf-preview-error-boundary-fallba import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context' import { captureException } from '../../../infrastructure/error-reporter' import { getPdfCachingMetrics } from '../util/metrics' -import { userContentDomainAccessCheckFailed } from '../../user-content-domain-access-check' -import { isURLOnUserContentDomain } from '../util/fetchFromCompileDomain' -import { isNetworkError } from '../../../utils/isNetworkError' -import OError from '@overleaf/o-error' function PdfJsViewer({ url, pdfFile }) { const { _id: projectId } = useProjectContext() @@ -130,15 +126,7 @@ function PdfJsViewer({ url, pdfFile }) { if (abortController.signal.aborted) return // The error is already logged at the call-site with additional context. if (err instanceof pdfJsWrapper.PDFJS.MissingPDFException) { - if ( - // 404 is unrelated to new domain - OError.getFullInfo(err).statusCode !== 404 && - isURLOnUserContentDomain(OError.getFullInfo(err).url) - ) { - setError('rendering-error-new-domain') - } else { - setError('rendering-error-expected') - } + setError('rendering-error-expected') } else { setError('rendering-error') } @@ -148,22 +136,7 @@ function PdfJsViewer({ url, pdfFile }) { .catch(error => { if (abortController.signal.aborted) return console.error(error) - if ( - isURLOnUserContentDomain(url) && - error instanceof pdfJsWrapper.PDFJS.UnexpectedResponseException - ) { - setError('rendering-error-new-domain') - } else if ( - isURLOnUserContentDomain(url) && - error.name === 'UnknownErrorException' && - (isNetworkError(error) || userContentDomainAccessCheckFailed()) - ) { - // For some reason, pdfJsWrapper.PDFJS.UnknownErrorException is - // not available for an instance check. - setError('rendering-error-new-domain') - } else { - setError('rendering-error') - } + setError('rendering-error') }) return () => { abortController.abort() diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.js b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.js index 3b1b5d238d..6c9d533352 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.js +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.js @@ -13,32 +13,6 @@ function PdfPreviewError({ error }) { const { startCompile } = useCompileContext() switch (error) { - case 'rendering-error-new-domain': - return ( - , - /* eslint-disable-next-line jsx-a11y/anchor-has-content */ - , - ]} - /> - } - level="warning" - /> - ) case 'rendering-error-expected': return ( , ]} /> + {getMeta('ol-compilesUserContentDomain') && ( + <> +
+
+ , + /* eslint-disable-next-line jsx-a11y/anchor-has-content */ +
, + ]} + /> + + )} ) diff --git a/services/web/frontend/js/features/pdf-preview/util/fetchFromCompileDomain.ts b/services/web/frontend/js/features/pdf-preview/util/fetchFromCompileDomain.ts deleted file mode 100644 index 7ac76313e9..0000000000 --- a/services/web/frontend/js/features/pdf-preview/util/fetchFromCompileDomain.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { isNetworkError } from '../../../utils/isNetworkError' -import getMeta from '../../../utils/meta' -import OError from '@overleaf/o-error' -import { postJSON } from '../../../infrastructure/fetch-json' -import { isSplitTestEnabled } from '../../../utils/splitTestUtils' - -let useFallbackDomainUntil = performance.now() -const ONE_HOUR_IN_MS = 1000 * 60 * 60 - -class MaybeBlockedByProxyError extends OError {} - -function checkForBlockingByProxy(url: string, res: Response) { - const statusCode = res.status - switch (statusCode) { - case 200: // full response - case 206: // range response - case 404: // file not found - case 416: // range not found - return - default: - throw new MaybeBlockedByProxyError('request might be blocked by proxy', { - res, - url, - statusCode, - }) - } -} - -export function isURLOnUserContentDomain(url: string) { - const userContentDomain = getMeta('ol-compilesUserContentDomain') - return ( - userContentDomain && - url && - new URL(url).hostname === new URL(userContentDomain).hostname - ) -} - -export async function fetchFromCompileDomain(url: string, init: RequestInit) { - let isUserContentDomain = isURLOnUserContentDomain(url) - const fallbackAllowed = !isSplitTestEnabled('force-new-compile-domain') - - if (fallbackAllowed && useFallbackDomainUntil > performance.now()) { - isUserContentDomain = false - url = withFallbackCompileDomain(url) - } - try { - const res = await fetch(url, init) - if (isUserContentDomain) { - // Only throw a MaybeBlockedByProxyError when the request will be retried - // on the fallback domain below. - checkForBlockingByProxy(url, res) - } - return res - } catch (err) { - if ( - fallbackAllowed && - isUserContentDomain && - (isNetworkError(err) || err instanceof MaybeBlockedByProxyError) - ) { - try { - const res = await fetch(withFallbackCompileDomain(url), init) - // Only switch to the fallback when fetch does not throw there as well. - if (useFallbackDomainUntil < performance.now()) { - useFallbackDomainUntil = performance.now() + ONE_HOUR_IN_MS - recordFallbackUsage() - } - return res - } catch (err2: any) { - throw OError.tag(err2, 'fallback request failed', { - errUserContentDomain: err, - }) - } - } - throw err - } -} - -export function swapDomain(url: string, domain: string) { - const u = new URL(url) - u.hostname = new URL(domain).hostname - return u.href -} - -function withFallbackCompileDomain(url: string) { - return swapDomain(url, getMeta('ol-fallbackCompileDomain')) -} - -function recordFallbackUsage() { - setTimeout(() => { - postJSON('/record-user-content-domain-fallback-usage').catch(() => {}) - }, 1_000) -} diff --git a/services/web/frontend/js/features/pdf-preview/util/output-files.js b/services/web/frontend/js/features/pdf-preview/util/output-files.js index 586356461a..e04a33c0e9 100644 --- a/services/web/frontend/js/features/pdf-preview/util/output-files.js +++ b/services/web/frontend/js/features/pdf-preview/util/output-files.js @@ -3,8 +3,6 @@ import HumanReadableLogs from '../../../ide/human-readable-logs/HumanReadableLog import BibLogParser from '../../../ide/log-parser/bib-log-parser' import { v4 as uuid } from 'uuid' import { enablePdfCaching } from './pdf-caching-flags' -import { fetchFromCompileDomain, swapDomain } from './fetchFromCompileDomain' -import { userContentDomainAccessCheckPassed } from '../../user-content-domain-access-check' // Warnings that may disappear after a second LaTeX pass const TRANSIENT_WARNING_REGEX = /^(Reference|Citation).+undefined on input line/ @@ -29,8 +27,7 @@ export function handleOutputFiles(outputFiles, projectId, data) { outputFile.pdfUrl = `${buildURL( outputFile, - data.pdfDownloadDomain, - data.enableHybridPdfDownload + data.pdfDownloadDomain )}?${params}` // build the URL for downloading the PDF @@ -72,10 +69,9 @@ export const handleLogFiles = async (outputFiles, data, signal) => { if (logFile) { try { - const response = await fetchFromCompileDomain( - buildURL(logFile, data.pdfDownloadDomain, data.enableHybridPdfDownload), - { signal } - ) + const response = await fetch(buildURL(logFile, data.pdfDownloadDomain), { + signal, + }) result.log = await response.text() @@ -108,10 +104,9 @@ export const handleLogFiles = async (outputFiles, data, signal) => { } for (const blgFile of blgFiles) { try { - const response = await fetchFromCompileDomain( - buildURL(blgFile, data.pdfDownloadDomain, data.enableHybridPdfDownload), - { signal } - ) + const response = await fetch(buildURL(blgFile, data.pdfDownloadDomain), { + signal, + }) const log = await response.text() @@ -166,20 +161,7 @@ export function buildLogEntryAnnotations(entries, fileTreeManager) { return logEntryAnnotations } -function buildURL(file, pdfDownloadDomain, enableHybridPdfDownload) { - const userContentDomain = getMeta('ol-compilesUserContentDomain') - if ( - enableHybridPdfDownload && - userContentDomainAccessCheckPassed() && - file.build && - userContentDomain - ) { - // This user is enrolled in the hybrid download of compile output. - // The access check passed, so try to use the new user content domain. - // Downloads from the compiles domains must include a build id. - // The build id is used implicitly for access control. - return swapDomain(`${pdfDownloadDomain}${file.url}`, userContentDomain) - } +function buildURL(file, pdfDownloadDomain) { if (file.build && pdfDownloadDomain) { // Downloads from the compiles domain must include a build id. // The build id is used implicitly for access control. diff --git a/services/web/frontend/js/features/pdf-preview/util/pdf-caching-transport.js b/services/web/frontend/js/features/pdf-preview/util/pdf-caching-transport.js index 626570b76f..017d488eb0 100644 --- a/services/web/frontend/js/features/pdf-preview/util/pdf-caching-transport.js +++ b/services/web/frontend/js/features/pdf-preview/util/pdf-caching-transport.js @@ -10,8 +10,6 @@ import { trackPdfDownloadEnabled, } from './pdf-caching-flags' import { isNetworkError } from '../../../utils/isNetworkError' -import { isSplitTestEnabled } from '../../../utils/splitTestUtils' -import { isURLOnUserContentDomain } from './fetchFromCompileDomain' // 30 seconds: The shutdown grace period of a clsi pre-emp instance. const STALE_OUTPUT_REQUEST_THRESHOLD_MS = 30 * 1000 @@ -78,10 +76,6 @@ export function generatePdfCachingTransportFactory(PDFJS) { end, metrics, }) - const isExpectedFailureOnNewCompileDomain = err => - isSplitTestEnabled('force-new-compile-domain') && - isURLOnUserContentDomain(OError.getFullInfo(err).url) && - OError.getFullInfo(err).responseSize !== this.pdfFile.size const isStaleOutputRequest = () => performance.now() - this.startTime > STALE_OUTPUT_REQUEST_THRESHOLD_MS @@ -97,9 +91,8 @@ export function generatePdfCachingTransportFactory(PDFJS) { // - requests for the main output.pdf file // A fallback request would not be able to retrieve the PDF either. const isExpectedError = err => - ((is404(err) || isNetworkError(err)) && - (isStaleOutputRequest() || isFromOutputPDFRequest(err))) || - isExpectedFailureOnNewCompileDomain(err) + (is404(err) || isNetworkError(err)) && + (isStaleOutputRequest() || isFromOutputPDFRequest(err)) fetchRange({ url: this.url, @@ -120,8 +113,6 @@ export function generatePdfCachingTransportFactory(PDFJS) { if (isExpectedError(err)) { if (is404(err)) { // A regular pdf-js request would have seen this 404 as well. - } else if (isExpectedFailureOnNewCompileDomain(err)) { - // A regular pdf-js request would have seen this proxy-error as well. } else { // Flaky network, switch back to regular pdf-js requests. metrics.failedCount++ diff --git a/services/web/frontend/js/features/pdf-preview/util/pdf-caching.js b/services/web/frontend/js/features/pdf-preview/util/pdf-caching.js index 4a2a40a571..0e3a40d730 100644 --- a/services/web/frontend/js/features/pdf-preview/util/pdf-caching.js +++ b/services/web/frontend/js/features/pdf-preview/util/pdf-caching.js @@ -1,5 +1,4 @@ import OError from '@overleaf/o-error' -import { fetchFromCompileDomain } from './fetchFromCompileDomain' const PDF_JS_CHUNK_SIZE = 128 * 1024 const MAX_SUB_REQUEST_COUNT = 4 @@ -485,7 +484,7 @@ export async function fallbackRequest({ url, start, end, abortSignal }) { headers: { Range: `bytes=${start}-${end - 1}` }, signal: abortSignal, } - const response = await fetchFromCompileDomain(url, init) + const response = await fetch(url, init) checkChunkResponse(response, end - start, init) return await response.arrayBuffer() } catch (e) { @@ -564,7 +563,7 @@ async function fetchChunk({ // result all the browser cache keys (aka urls) get invalidated. // We memorize the previous browser cache keys in `cachedUrls`. try { - const response = await fetchFromCompileDomain(oldUrl, init) + const response = await fetch(oldUrl, init) if (response.status === 200) { checkChunkResponse(response, estimatedSize, init) metrics.oldUrlHitCount += 1 @@ -579,7 +578,7 @@ async function fetchChunk({ // Fallback to the latest url. } } - const response = await fetchFromCompileDomain(url, init) + const response = await fetch(url, init) checkChunkResponse(response, estimatedSize, init) if (chunk.hash) cachedUrls.set(chunk.hash, url) return response 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 deleted file mode 100644 index 4236603d2b..0000000000 --- a/services/web/frontend/js/features/user-content-domain-access-check/index.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { - checkChunkResponse, - estimateSizeOfMultipartResponse, - getMultipartBoundary, - resolveMultiPartResponses, -} from '../pdf-preview/util/pdf-caching' -import getMeta from '../../utils/meta' -import OError from '@overleaf/o-error' -import { captureException } from '../../infrastructure/error-reporter' -import { postJSON } from '../../infrastructure/fetch-json' -import { - isSplitTestEnabled, - parseIntFromSplitTest, -} from '../../utils/splitTestUtils' - -const MAX_CHECKS_PER_PAGE_LOAD = parseIntFromSplitTest( - 'user-content-domain-access-check-max-checks', - 3 -) -const INITIAL_DELAY_MS = parseIntFromSplitTest( - 'user-content-domain-access-check-delay', - 30_000 -) -const TIMEOUT_MS = 30_000 -const FULL_SIZE = 739 -const FULL_HASH = - 'b7d25591c18da373709d3d88ddf5eeab0b5089359e580f051314fd8935df0b73' -const CHUNKS = [ - { - start: 0, - end: 21, - hash: 'd2ad9cbf1bc669646c0dfc43fa3167d30ab75077bb46bc9e3624b9e7e168abc2', - }, - { - start: 21, - end: 42, - hash: 'd6d110ec0f3f4e27a4050bc2be9c5552cc9092f86b74fec75072c2c9e8483454', - }, - { - start: 42, - end: 64, - hash: '8278914487a3a099c9af5aa22ed836d6587ca0beb7bf9a059fb0409667b3eb3d', - }, -] - -function pickZone() { - const x = Math.random() - switch (true) { - case x > 0.66: - return 'b' - case x > 0.33: - return 'c' - default: - return 'd' - } -} - -function arrayLikeToHex(a: Uint8Array) { - return Array.from(a) - .map(i => i.toString(16).padStart(2, '0')) - .join('') -} - -async function hashBody(body: ArrayBuffer) { - const digest = await crypto.subtle.digest('SHA-256', body) - return arrayLikeToHex(new Uint8Array(digest)) -} - -async function checkHash( - res: Response, - data: ArrayBuffer, - expectedHash: string -) { - const actualHash = await hashBody(data) - if (actualHash !== expectedHash) { - throw new OError('content hash mismatch', { - actualHash, - expectedHash, - headers: Object.fromEntries(res.headers.entries()), - }) - } -} - -function randomHex(bytes: number) { - const buf = new Uint8Array(bytes) - crypto.getRandomValues(buf) - return arrayLikeToHex(buf) -} - -function genBuildId() { - const date = Date.now().toString(16) - const random = randomHex(8) - return `${date}-${random}` -} - -async function singleCheck( - url: string, - init: RequestInit, - estimatedSize: number, - expectedHash: string, - chunks?: Array -) { - const ac = new AbortController() - setTimeout(() => ac.abort(), TIMEOUT_MS) - init.signal = ac.signal - init.cache = 'no-store' - - const res = await fetch(url, init) - checkChunkResponse(res, estimatedSize, init) - - const body = await res.arrayBuffer() - if (chunks) { - const boundary = getMultipartBoundary(res, chunks) - const parts = resolveMultiPartResponses({ - file: { size: FULL_SIZE }, - chunks, - data: new Uint8Array(body), - boundary, - metrics: {}, - }) - for (const part of parts) { - await checkHash(res, part.data, part.chunk.hash) - } - } else { - await checkHash(res, body, expectedHash) - } -} - -export async function checkUserContentDomainAccess( - compileDomainOrigin: string -) { - // Note: The ids are zero prefixed. No actual user/project uses these ids. - // mongo-id 000000000000000000000000 -> 1970-01-01T00:00:00.000Z - // mongo-id 000000010000000000000000 -> 1970-01-01T00:00:01.000Z - // mongo-id 100000000000000000000000 -> 1978-07-04T21:24:16.000Z - // This allows us to distinguish between check-traffic and regular output - // traffic. - const projectId = `0${randomHex(12).slice(1)}` - const userId = `0${randomHex(12).slice(1)}` - const buildId = genBuildId() - const zone = pickZone() - const urls = [] - if (getMeta('ol-user_id')) { - // Logged-in user - urls.push( - `${compileDomainOrigin}/zone/${zone}/project/${projectId}/user/${userId}/build/${buildId}/output/output.pdf` - ) - } else { - // Anonymous user - urls.push( - `${compileDomainOrigin}/zone/${zone}/project/${projectId}/build/${buildId}/output/output.pdf` - ) - } - - const cases = [] - for (const url of urls) { - // full download - cases.push({ - url, - init: {}, - estimatedSize: FULL_SIZE, - hash: FULL_HASH, - }) - - // range request - const chunk = CHUNKS[0] - cases.push({ - url, - init: { - headers: { - Range: `bytes=${chunk.start}-${chunk.end - 1}`, - }, - }, - estimatedSize: chunk.end - chunk.start, - hash: chunk.hash, - }) - - // multipart request - cases.push({ - url, - init: { - headers: { - Range: `bytes=${CHUNKS.map(c => `${c.start}-${c.end - 1}`).join( - ',' - )}`, - }, - }, - estimatedSize: estimateSizeOfMultipartResponse(CHUNKS), - hash: chunk.hash, - chunks: CHUNKS, - }) - } - - let failed = 0 - let ignoreResult = false - const epochBeforeCheck = networkEpoch - await Promise.all( - cases.map(async ({ url, init, estimatedSize, hash, chunks }) => { - 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. - ignoreResult = true - } - if (ignoreResult) return - - failed++ - OError.tag(err, 'user-content-domain-access-check failed', { - url, - init, - }) - if ( - isSplitTestEnabled('report-user-content-domain-access-check-error') - ) { - captureException(err, { - tags: { compileDomain: new URL(compileDomainOrigin).hostname }, - }) - } else { - console.error(OError.getFullStack(err), OError.getFullInfo(err)) - } - } - }) - ) - if (ignoreResult) return false - - try { - await postJSON('/record-user-content-domain-access-check-result', { - body: { - failed, - succeeded: cases.length - failed, - isOldDomain: - compileDomainOrigin === getMeta('ol-fallbackCompileDomain'), - }, - }) - } catch (e) {} - - return failed === 0 -} - -const ACCESS_CHECK_PASSED = 'passed' -const ACCESS_CHECK_PENDING = 'pending' -const ACCESS_CHECK_FAILED = 'failed' -let accessCheckStatus = ACCESS_CHECK_PENDING - -export function userContentDomainAccessCheckPassed() { - return accessCheckStatus === ACCESS_CHECK_PASSED -} -export function userContentDomainAccessCheckFailed() { - return accessCheckStatus === ACCESS_CHECK_FAILED -} - -let networkEpoch = performance.now() -window.addEventListener('offline', () => { - // We are offline. Abort any scheduled check. - clearTimeout(lastScheduledCheck) - accessCheckStatus = ACCESS_CHECK_PENDING - networkEpoch = performance.now() -}) -window.addEventListener('online', () => { - // We are online again. Schedule another check for this network. - accessCheckStatus = ACCESS_CHECK_PENDING - 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 - // @ts-ignore - navigator.connection.addEventListener('change', () => { - // The network changed. Schedule another check for it. - accessCheckStatus = ACCESS_CHECK_PENDING - networkEpoch = performance.now() - scheduleUserContentDomainAccessCheck() - }) -} catch (e) {} - -let lastScheduledCheck: number -let remainingChecks = MAX_CHECKS_PER_PAGE_LOAD -export function scheduleUserContentDomainAccessCheck() { - 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 (userContentDomainAccessCheckPassed()) return - if (remainingChecks === 0) { - recordMaxAccessChecksHit() - } - if (remainingChecks-- <= 0) return - if (isSplitTestEnabled('access-check-for-old-compile-domain')) { - checkUserContentDomainAccess(getMeta('ol-fallbackCompileDomain')).catch( - () => {} - ) - } - checkUserContentDomainAccess(getMeta('ol-compilesUserContentDomain')) - .then(ok => { - accessCheckStatus = ok ? ACCESS_CHECK_PASSED : ACCESS_CHECK_FAILED - }) - .catch(err => { - captureException(err) - }) - }, INITIAL_DELAY_MS) -} - -function recordMaxAccessChecksHit() { - postJSON('/record-user-content-domain-max-access-checks-hit').catch(() => {}) -} diff --git a/services/web/frontend/js/ide.js b/services/web/frontend/js/ide.js index 77badf44a9..e1b9a6066f 100644 --- a/services/web/frontend/js/ide.js +++ b/services/web/frontend/js/ide.js @@ -73,7 +73,6 @@ import './features/history/controllers/history-file-tree-controller' import { cleanupServiceWorker } from './utils/service-worker-cleanup' import { reportCM6Perf } from './infrastructure/cm6-performance' import { reportAcePerf } from './ide/editor/ace-performance' -import { scheduleUserContentDomainAccessCheck } from './features/user-content-domain-access-check' App.controller( 'IdeController', @@ -487,7 +486,6 @@ If the project has been renamed please look in your project list for a new proje ) cleanupServiceWorker() -scheduleUserContentDomainAccessCheck() angular.module('SharelatexApp').config(function ($provide) { $provide.decorator('$browser', [ diff --git a/services/web/frontend/js/shared/context/detach-compile-context.js b/services/web/frontend/js/shared/context/detach-compile-context.js index 14491a826f..fd743fe5a2 100644 --- a/services/web/frontend/js/shared/context/detach-compile-context.js +++ b/services/web/frontend/js/shared/context/detach-compile-context.js @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useMemo } from 'react' +import { createContext, useContext, useMemo } from 'react' import PropTypes from 'prop-types' import { useLocalCompileContext, @@ -7,7 +7,6 @@ import { import useDetachStateWatcher from '../hooks/use-detach-state-watcher' import useDetachAction from '../hooks/use-detach-action' import useCompileTriggers from '../../features/pdf-preview/hooks/use-compile-triggers' -import getMeta from '../../utils/meta' export const DetachCompileContext = createContext() @@ -33,7 +32,6 @@ export function DetachCompileProvider({ children }) { editedSinceCompileStarted: _editedSinceCompileStarted, error: _error, fileList: _fileList, - forceNewDomainVariant: _forceNewDomainVariant, hasChanges: _hasChanges, highlights: _highlights, lastCompileOptions: _lastCompileOptions, @@ -125,12 +123,6 @@ export function DetachCompileProvider({ children }) { 'detacher', 'detached' ) - const [forceNewDomainVariant] = useDetachStateWatcher( - 'forceNewDomainVariant', - _forceNewDomainVariant, - 'detacher', - 'detached' - ) const [hasChanges] = useDetachStateWatcher( 'hasChanges', _hasChanges, @@ -375,11 +367,6 @@ export function DetachCompileProvider({ children }) { ) useCompileTriggers(startCompile, setChangedAt, setSavedAt) - useEffect(() => { - // Sync the split test variant across the editor and pdf-detach. - const variants = getMeta('ol-splitTestVariants') || {} - variants['force-new-compile-domain'] = forceNewDomainVariant - }, [forceNewDomainVariant]) const value = useMemo( () => ({ @@ -395,7 +382,6 @@ export function DetachCompileProvider({ children }) { editedSinceCompileStarted, error, fileList, - forceNewDomainVariant, hasChanges, highlights, lastCompileOptions, @@ -449,7 +435,6 @@ export function DetachCompileProvider({ children }) { error, editedSinceCompileStarted, fileList, - forceNewDomainVariant, hasChanges, highlights, lastCompileOptions, diff --git a/services/web/frontend/js/shared/context/local-compile-context.js b/services/web/frontend/js/shared/context/local-compile-context.js index 14db362f5d..fc50661c48 100644 --- a/services/web/frontend/js/shared/context/local-compile-context.js +++ b/services/web/frontend/js/shared/context/local-compile-context.js @@ -29,7 +29,6 @@ import { useEditorContext } from './editor-context' import { buildFileList } from '../../features/pdf-preview/util/file-list' import { useLayoutContext } from './layout-context' import { useUserContext } from './user-context' -import getMeta from '../../utils/meta' export const LocalCompileContext = createContext() @@ -44,7 +43,6 @@ export const CompileContextPropTypes = { draft: PropTypes.bool.isRequired, error: PropTypes.string, fileList: PropTypes.object, - forceNewDomainVariant: PropTypes.string, hasChanges: PropTypes.bool.isRequired, highlights: PropTypes.arrayOf(PropTypes.object), logEntries: PropTypes.object, @@ -174,11 +172,6 @@ export function LocalCompileProvider({ children }) { // the list of files that can be downloaded const [fileList, setFileList] = useState() - // Split test variant for disabling the fallback, refreshed on re-compile. - const [forceNewDomainVariant, setForceNewDomainVariant] = useState( - getMeta('ol-splitTestVariants')?.['force-new-compile-domain'] - ) - // the raw contents of the log file const [rawLog, setRawLog] = useState() @@ -323,7 +316,6 @@ export function LocalCompileProvider({ children }) { setShowFasterCompilesFeedbackUI( Boolean(data.showFasterCompilesFeedbackUI) ) - setForceNewDomainVariant(data.forceNewDomainVariant || 'default') if (data.outputFiles) { const outputFiles = new Map() @@ -561,7 +553,6 @@ export function LocalCompileProvider({ children }) { editedSinceCompileStarted, error, fileList, - forceNewDomainVariant, hasChanges, highlights, lastCompileOptions, @@ -616,7 +607,6 @@ export function LocalCompileProvider({ children }) { editedSinceCompileStarted, error, fileList, - forceNewDomainVariant, hasChanges, highlights, lastCompileOptions, diff --git a/services/web/locales/en.json b/services/web/locales/en.json index f693bd9e12..0c31e815a7 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1004,7 +1004,7 @@ "need_to_leave": "Need to leave?", "need_to_upgrade_for_more_collabs": "You need to upgrade your account to add more collaborators", "need_to_upgrade_for_more_collabs_variant": "You have reached the maximum number of collaborators. Upgrade your account to add more.", - "new_compile_domain_trouble_shooting": "We are migrating PDF downloads to a new domain. It looks like something is blocking your browser from accessing that new domain, <0>__compilesUserContentDomain__. This could be caused by network blocking or a strict browser plugin rule. Please follow our <1>troubleshooting guide.", + "new_compile_domain_notice": "We’ve recently migrated PDF downloads to a new domain. Something might be blocking your browser from accessing that new domain, <0>__compilesUserContentDomain__. This could be caused by network blocking or a strict browser plugin rule. Please follow our <1>troubleshooting guide.", "new_file": "New File", "new_folder": "New Folder", "new_name": "New Name", diff --git a/services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx index 887c44961a..9a3cefc7af 100644 --- a/services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx +++ b/services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx @@ -23,6 +23,10 @@ const Layout: FC<{ layout: string; view?: string }> = ({ layout, view }) => { describe('', function () { beforeEach(function () { window.metaAttributesCache.set('ol-preventCompileOnLoad', true) + window.metaAttributesCache.set( + 'ol-compilesUserContentDomain', + 'https://compiles-user.dev-overleaf.com' + ) cy.interceptEvents() }) diff --git a/services/web/test/unit/src/Compile/CompileControllerTests.js b/services/web/test/unit/src/Compile/CompileControllerTests.js index 1b1889ff82..45e8da9450 100644 --- a/services/web/test/unit/src/Compile/CompileControllerTests.js +++ b/services/web/test/unit/src/Compile/CompileControllerTests.js @@ -126,8 +126,6 @@ describe('CompileController', function () { }, ], pdfDownloadDomain: 'https://compiles.overleaf.test', - enableHybridPdfDownload: false, - forceNewDomainVariant: 'default', }) ) }) @@ -170,8 +168,6 @@ describe('CompileController', function () { }, ], pdfDownloadDomain: 'https://compiles.overleaf.test/zone/b', - enableHybridPdfDownload: false, - forceNewDomainVariant: 'default', }) ) }) @@ -213,8 +209,6 @@ describe('CompileController', function () { JSON.stringify({ status: this.status, outputFiles: this.outputFiles, - enableHybridPdfDownload: false, - forceNewDomainVariant: 'default', }) ) })