| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- import OError from '@overleaf/o-error'
- import { fallbackRequest, fetchRange } from './pdf-caching'
- import { captureException } from '../../../infrastructure/error-reporter'
- import { getPdfCachingMetrics } from './metrics'
- import {
- cachedUrlLookupEnabled,
- enablePdfCaching,
- prefetchingEnabled,
- prefetchLargeEnabled,
- trackPdfDownloadEnabled,
- } from './pdf-caching-flags'
- export function generatePdfCachingTransportFactory(PDFJS) {
- // NOTE: The custom transport can be used for tracking download volume.
- if (!enablePdfCaching && !trackPdfDownloadEnabled) {
- return () => null
- }
- const usageScore = new Map()
- const cachedUrls = new Map()
- const metrics = Object.assign(getPdfCachingMetrics(), {
- failedCount: 0,
- failedOnce: false,
- tooMuchBandwidthCount: 0,
- tooManyRequestsCount: 0,
- cachedCount: 0,
- cachedBytes: 0,
- fetchedCount: 0,
- fetchedBytes: 0,
- requestedCount: 0,
- requestedBytes: 0,
- oldUrlHitCount: 0,
- oldUrlMissCount: 0,
- enablePdfCaching,
- prefetchingEnabled,
- prefetchLargeEnabled,
- cachedUrlLookupEnabled,
- })
- const verifyChunks =
- new URLSearchParams(window.location.search).get('verify_chunks') === 'true'
- class PDFDataRangeTransport extends PDFJS.PDFDataRangeTransport {
- constructor({ url, pdfFile, abortController, handleFetchError }) {
- super(pdfFile.size, new Uint8Array())
- this.url = url
- this.pdfFile = pdfFile
- this.handleFetchError = handleFetchError
- this.abortController = abortController
- }
- abort() {
- this.abortController.abort()
- }
- requestDataRange(start, end) {
- const abortSignal = this.abortController.signal
- const errorInfo = {
- pdfFile: this.pdfFile,
- pdfUrl: this.url,
- start,
- end,
- metrics,
- }
- fetchRange({
- url: this.url,
- start,
- end,
- file: this.pdfFile,
- metrics,
- usageScore,
- cachedUrls,
- verifyChunks,
- prefetchingEnabled,
- prefetchLargeEnabled,
- cachedUrlLookupEnabled,
- abortSignal,
- })
- .catch(err => {
- if (abortSignal.aborted) return
- if (
- err.message === 'non successful response status: 404' &&
- OError.getFullInfo(err).url === this.url
- ) {
- // Do not consider a 404 on the main pdf url as pdf caching failure.
- // Still, bail out during the initial launch phase.
- metrics.failedOnce = true
- throw new PDFJS.MissingPDFException()
- }
- metrics.failedCount++
- metrics.failedOnce = true
- if (!enablePdfCaching) {
- throw err // This was a fallback request already. Do not retry.
- }
- err = OError.tag(err, 'optimized pdf download error', errorInfo)
- console.error(err)
- captureException(err, { tags: { fromPdfCaching: true } })
- return fallbackRequest({ url: this.url, start, end, abortSignal })
- })
- .then(blob => {
- if (abortSignal.aborted) return
- this.onDataRange(start, blob)
- })
- .catch(err => {
- if (abortSignal.aborted) return
- err = OError.tag(err, 'fatal pdf download error', errorInfo)
- console.error(err)
- if (!(err instanceof PDFJS.MissingPDFException)) {
- captureException(err, { tags: { fromPdfCaching: true } })
- }
- // Signal error for (subsequent) page load.
- this.handleFetchError(err)
- })
- }
- }
- return function ({ url, pdfFile, abortController, handleFetchError }) {
- if (metrics.failedOnce) {
- // Disable pdf caching once any fetch request failed.
- // Be trigger-happy here until we reached a stable state of the feature.
- return null
- }
- return new PDFDataRangeTransport({
- url,
- pdfFile,
- abortController,
- handleFetchError,
- })
- }
- }
|