fetchFromCompileDomain.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import { isNetworkError } from '../../../utils/isNetworkError'
  2. import getMeta from '../../../utils/meta'
  3. import OError from '@overleaf/o-error'
  4. import { postJSON } from '../../../infrastructure/fetch-json'
  5. import { isSplitTestEnabled } from '../../../utils/splitTestUtils'
  6. let useFallbackDomainUntil = performance.now()
  7. const ONE_HOUR_IN_MS = 1000 * 60 * 60
  8. class MaybeBlockedByProxyError extends OError {}
  9. function checkForBlockingByProxy(url: string, res: Response) {
  10. const statusCode = res.status
  11. switch (statusCode) {
  12. case 200: // full response
  13. case 206: // range response
  14. case 404: // file not found
  15. case 416: // range not found
  16. return
  17. default:
  18. throw new MaybeBlockedByProxyError('request might be blocked by proxy', {
  19. res,
  20. url,
  21. statusCode,
  22. })
  23. }
  24. }
  25. export function isURLOnUserContentDomain(url: string) {
  26. const userContentDomain = getMeta('ol-compilesUserContentDomain')
  27. return (
  28. userContentDomain &&
  29. url &&
  30. new URL(url).hostname === new URL(userContentDomain).hostname
  31. )
  32. }
  33. export async function fetchFromCompileDomain(url: string, init: RequestInit) {
  34. let isUserContentDomain = isURLOnUserContentDomain(url)
  35. const fallbackAllowed = !isSplitTestEnabled('force-new-compile-domain')
  36. if (fallbackAllowed && useFallbackDomainUntil > performance.now()) {
  37. isUserContentDomain = false
  38. url = withFallbackCompileDomain(url)
  39. }
  40. try {
  41. const res = await fetch(url, init)
  42. if (isUserContentDomain) {
  43. // Only throw a MaybeBlockedByProxyError when the request will be retried
  44. // on the fallback domain below.
  45. checkForBlockingByProxy(url, res)
  46. }
  47. return res
  48. } catch (err) {
  49. if (
  50. fallbackAllowed &&
  51. isUserContentDomain &&
  52. (isNetworkError(err) || err instanceof MaybeBlockedByProxyError)
  53. ) {
  54. try {
  55. const res = await fetch(withFallbackCompileDomain(url), init)
  56. // Only switch to the fallback when fetch does not throw there as well.
  57. if (useFallbackDomainUntil < performance.now()) {
  58. useFallbackDomainUntil = performance.now() + ONE_HOUR_IN_MS
  59. recordFallbackUsage()
  60. }
  61. return res
  62. } catch (err2: any) {
  63. throw OError.tag(err2, 'fallback request failed', {
  64. errUserContentDomain: err,
  65. })
  66. }
  67. }
  68. throw err
  69. }
  70. }
  71. export function swapDomain(url: string, domain: string) {
  72. const u = new URL(url)
  73. u.hostname = new URL(domain).hostname
  74. return u.href
  75. }
  76. function withFallbackCompileDomain(url: string) {
  77. return swapDomain(url, getMeta('ol-fallbackCompileDomain'))
  78. }
  79. function recordFallbackUsage() {
  80. setTimeout(() => {
  81. postJSON('/record-user-content-domain-fallback-usage').catch(() => {})
  82. }, 1_000)
  83. }