pdf-caching-transport.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import OError from '@overleaf/o-error'
  2. import { fallbackRequest, fetchRange } from './pdf-caching'
  3. import { captureException } from '../../../infrastructure/error-reporter'
  4. import { getPdfCachingMetrics } from './metrics'
  5. import {
  6. cachedUrlLookupEnabled,
  7. enablePdfCaching,
  8. prefetchingEnabled,
  9. prefetchLargeEnabled,
  10. trackPdfDownloadEnabled,
  11. } from './pdf-caching-flags'
  12. export function generatePdfCachingTransportFactory(PDFJS) {
  13. // NOTE: The custom transport can be used for tracking download volume.
  14. if (!enablePdfCaching && !trackPdfDownloadEnabled) {
  15. return () => null
  16. }
  17. const usageScore = new Map()
  18. const cachedUrls = new Map()
  19. const metrics = Object.assign(getPdfCachingMetrics(), {
  20. failedCount: 0,
  21. failedOnce: false,
  22. tooMuchBandwidthCount: 0,
  23. tooManyRequestsCount: 0,
  24. cachedCount: 0,
  25. cachedBytes: 0,
  26. fetchedCount: 0,
  27. fetchedBytes: 0,
  28. requestedCount: 0,
  29. requestedBytes: 0,
  30. oldUrlHitCount: 0,
  31. oldUrlMissCount: 0,
  32. enablePdfCaching,
  33. prefetchingEnabled,
  34. prefetchLargeEnabled,
  35. cachedUrlLookupEnabled,
  36. })
  37. const verifyChunks =
  38. new URLSearchParams(window.location.search).get('verify_chunks') === 'true'
  39. class PDFDataRangeTransport extends PDFJS.PDFDataRangeTransport {
  40. constructor({ url, pdfFile, abortController, handleFetchError }) {
  41. super(pdfFile.size, new Uint8Array())
  42. this.url = url
  43. this.pdfFile = pdfFile
  44. this.handleFetchError = handleFetchError
  45. this.abortController = abortController
  46. }
  47. abort() {
  48. this.abortController.abort()
  49. }
  50. requestDataRange(start, end) {
  51. const abortSignal = this.abortController.signal
  52. const errorInfo = {
  53. pdfFile: this.pdfFile,
  54. pdfUrl: this.url,
  55. start,
  56. end,
  57. metrics,
  58. }
  59. fetchRange({
  60. url: this.url,
  61. start,
  62. end,
  63. file: this.pdfFile,
  64. metrics,
  65. usageScore,
  66. cachedUrls,
  67. verifyChunks,
  68. prefetchingEnabled,
  69. prefetchLargeEnabled,
  70. cachedUrlLookupEnabled,
  71. abortSignal,
  72. })
  73. .catch(err => {
  74. if (abortSignal.aborted) return
  75. if (
  76. err.message === 'non successful response status: 404' &&
  77. OError.getFullInfo(err).url === this.url
  78. ) {
  79. // Do not consider a 404 on the main pdf url as pdf caching failure.
  80. // Still, bail out during the initial launch phase.
  81. metrics.failedOnce = true
  82. throw new PDFJS.MissingPDFException()
  83. }
  84. metrics.failedCount++
  85. metrics.failedOnce = true
  86. if (!enablePdfCaching) {
  87. throw err // This was a fallback request already. Do not retry.
  88. }
  89. err = OError.tag(err, 'optimized pdf download error', errorInfo)
  90. console.error(err)
  91. captureException(err, { tags: { fromPdfCaching: true } })
  92. return fallbackRequest({ url: this.url, start, end, abortSignal })
  93. })
  94. .then(blob => {
  95. if (abortSignal.aborted) return
  96. this.onDataRange(start, blob)
  97. })
  98. .catch(err => {
  99. if (abortSignal.aborted) return
  100. err = OError.tag(err, 'fatal pdf download error', errorInfo)
  101. console.error(err)
  102. if (!(err instanceof PDFJS.MissingPDFException)) {
  103. captureException(err, { tags: { fromPdfCaching: true } })
  104. }
  105. // Signal error for (subsequent) page load.
  106. this.handleFetchError(err)
  107. })
  108. }
  109. }
  110. return function ({ url, pdfFile, abortController, handleFetchError }) {
  111. if (metrics.failedOnce) {
  112. // Disable pdf caching once any fetch request failed.
  113. // Be trigger-happy here until we reached a stable state of the feature.
  114. return null
  115. }
  116. return new PDFDataRangeTransport({
  117. url,
  118. pdfFile,
  119. abortController,
  120. handleFetchError,
  121. })
  122. }
  123. }