fetchFromCompileDomain.ts 2.4 KB

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