WebApiManager.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import { callbackify } from 'util'
  2. import { setTimeout } from 'timers/promises'
  3. import logger from '@overleaf/logger'
  4. import Metrics from '@overleaf/metrics'
  5. import Settings from '@overleaf/settings'
  6. import {
  7. fetchNothing,
  8. fetchJson,
  9. RequestFailedError,
  10. } from '@overleaf/fetch-utils'
  11. import * as Errors from './Errors.js'
  12. import * as RedisManager from './RedisManager.js'
  13. let RETRY_TIMEOUT_MS = 5000
  14. async function getHistoryId(projectId) {
  15. Metrics.inc('history_id_cache_requests_total')
  16. const cachedHistoryId =
  17. await RedisManager.promises.getCachedHistoryId(projectId)
  18. if (cachedHistoryId) {
  19. Metrics.inc('history_id_cache_hits_total')
  20. return cachedHistoryId
  21. } else {
  22. const project = await _getProjectDetails(projectId)
  23. const historyId =
  24. project.overleaf &&
  25. project.overleaf.history &&
  26. project.overleaf.history.id
  27. if (historyId != null) {
  28. await RedisManager.promises.setCachedHistoryId(projectId, historyId)
  29. }
  30. return historyId
  31. }
  32. }
  33. async function requestResync(projectId) {
  34. try {
  35. await fetchNothing(
  36. `${Settings.apis.web.url}/project/${projectId}/history/resync`,
  37. {
  38. method: 'POST',
  39. signal: AbortSignal.timeout(6 * 60000),
  40. basicAuth: {
  41. user: Settings.apis.web.user,
  42. password: Settings.apis.web.pass,
  43. },
  44. }
  45. )
  46. } catch (err) {
  47. if (err instanceof RequestFailedError && err.response.status === 404) {
  48. throw new Errors.NotFoundError('got a 404 from web api').withCause(err)
  49. } else {
  50. throw err
  51. }
  52. }
  53. }
  54. async function _getProjectDetails(projectId, callback) {
  55. logger.debug({ projectId }, 'getting project details from web')
  56. let attempts = 0
  57. while (true) {
  58. attempts += 1
  59. try {
  60. return await fetchJson(
  61. `${Settings.apis.web.url}/project/${projectId}/details`,
  62. {
  63. signal: AbortSignal.timeout(16000),
  64. basicAuth: {
  65. user: Settings.apis.web.user,
  66. password: Settings.apis.web.pass,
  67. },
  68. }
  69. )
  70. } catch (err) {
  71. if (err instanceof RequestFailedError && err.response.status === 404) {
  72. throw new Errors.NotFoundError('got a 404 from web api').withCause(err)
  73. } else if (attempts < 2) {
  74. // retry after 5 seconds
  75. await setTimeout(RETRY_TIMEOUT_MS)
  76. } else {
  77. throw err
  78. }
  79. }
  80. }
  81. }
  82. /**
  83. * Adjust the retry timeout in tests
  84. */
  85. export async function setRetryTimeoutMs(timeoutMs) {
  86. RETRY_TIMEOUT_MS = timeoutMs
  87. }
  88. // EXPORTS
  89. const getHistoryIdCb = callbackify(getHistoryId)
  90. const requestResyncCb = callbackify(requestResync)
  91. export { getHistoryIdCb as getHistoryId, requestResyncCb as requestResync }
  92. export const promises = {
  93. getHistoryId,
  94. requestResync,
  95. }