Client.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import express from 'express'
  2. import { fetchJson, fetchNothing, fetchString } from '@overleaf/fetch-utils'
  3. import fs from 'node:fs'
  4. import fsPromises from 'node:fs/promises'
  5. import Settings from '@overleaf/settings'
  6. const host = Settings.apis.clsi.url
  7. function randomId() {
  8. // Avoid ids starting with 0, which get a dummy PDF served.
  9. return 'a' + Math.random().toString(16).slice(2)
  10. }
  11. function compile(projectId, data) {
  12. if (data) {
  13. // Enable pdf caching unless disabled explicitly.
  14. data.options = Object.assign({}, { enablePdfCaching: true }, data.options)
  15. }
  16. return fetchJson(`${host}/project/${projectId}/compile`, {
  17. method: 'POST',
  18. json: {
  19. compile: data,
  20. },
  21. })
  22. }
  23. async function stopCompile(projectId) {
  24. return await fetchNothing(`${host}/project/${projectId}/compile/stop`, {
  25. method: 'POST',
  26. })
  27. }
  28. async function clearCache(projectId) {
  29. await fetchNothing(`${host}/project/${projectId}`, {
  30. method: 'DELETE',
  31. })
  32. }
  33. function getOutputFile(response, type) {
  34. for (const file of response.compile.outputFiles) {
  35. if (file.type === type && file.url.match(`output.${type}`)) {
  36. return file
  37. }
  38. }
  39. return null
  40. }
  41. function runFakeFilestoreService(directory) {
  42. const app = express()
  43. app.use(express.static(directory))
  44. this.startFakeFilestoreApp(app)
  45. }
  46. function startFakeFilestoreApp(app) {
  47. let server
  48. before(function (done) {
  49. server = app.listen(error => {
  50. if (error) {
  51. done(new Error('error starting server: ' + error.message))
  52. } else {
  53. const addr = server.address()
  54. Settings.filestoreDomainOveride = `http://127.0.0.1:${addr.port}`
  55. done()
  56. }
  57. })
  58. })
  59. after(function (done) {
  60. server.close(done)
  61. })
  62. }
  63. function syncFromCode(projectId, file, line, column) {
  64. return syncFromCodeWithImage(projectId, file, line, column, '')
  65. }
  66. async function syncFromCodeWithImage(projectId, file, line, column, imageName) {
  67. const url = new URL(`${host}/project/${projectId}/sync/code`)
  68. url.searchParams.append('imageName', imageName)
  69. url.searchParams.append('file', file)
  70. url.searchParams.append('line', line)
  71. url.searchParams.append('column', column)
  72. return await fetchJson(url)
  73. }
  74. function syncFromPdf(projectId, page, h, v) {
  75. return syncFromPdfWithImage(projectId, page, h, v, '')
  76. }
  77. function syncFromPdfWithImage(projectId, page, h, v, imageName) {
  78. const url = new URL(`${host}/project/${projectId}/sync/pdf`)
  79. url.searchParams.append('imageName', imageName)
  80. url.searchParams.append('page', page)
  81. url.searchParams.append('h', h)
  82. url.searchParams.append('v', v)
  83. return fetchJson(url)
  84. }
  85. function wordcount(projectId, file) {
  86. const image = undefined
  87. return wordcountWithImage(projectId, file, image)
  88. }
  89. async function wordcountWithImage(projectId, file, image) {
  90. const url = new URL(`${host}/project/${projectId}/wordcount`)
  91. if (image) {
  92. url.searchParams.append('image', image)
  93. }
  94. url.searchParams.append('file', file)
  95. return await fetchJson(url)
  96. }
  97. async function compileDirectory(projectId, baseDirectory, directory) {
  98. const resources = []
  99. let entities = fs.readdirSync(`${baseDirectory}/${directory}`)
  100. let rootResourcePath = 'main.tex'
  101. while (entities.length > 0) {
  102. const entity = entities.pop()
  103. const stat = fs.statSync(`${baseDirectory}/${directory}/${entity}`)
  104. if (stat.isDirectory()) {
  105. entities = entities.concat(
  106. fs
  107. .readdirSync(`${baseDirectory}/${directory}/${entity}`)
  108. .map(subEntity => {
  109. if (subEntity === 'main.tex') {
  110. rootResourcePath = `${entity}/${subEntity}`
  111. }
  112. return `${entity}/${subEntity}`
  113. })
  114. )
  115. } else if (stat.isFile() && entity !== 'output.pdf') {
  116. const extension = entity.split('.').pop()
  117. if (
  118. [
  119. 'tex',
  120. 'bib',
  121. 'cls',
  122. 'sty',
  123. 'pdf_tex',
  124. 'Rtex',
  125. 'ist',
  126. 'md',
  127. 'Rmd',
  128. 'Rnw',
  129. ].indexOf(extension) > -1
  130. ) {
  131. resources.push({
  132. path: entity,
  133. content: fs
  134. .readFileSync(`${baseDirectory}/${directory}/${entity}`)
  135. .toString(),
  136. })
  137. } else if (
  138. ['eps', 'ttf', 'png', 'jpg', 'pdf', 'jpeg'].indexOf(extension) > -1
  139. ) {
  140. resources.push({
  141. path: entity,
  142. url: `http://filestore/${directory}/${entity}`,
  143. modified: stat.mtime,
  144. })
  145. }
  146. }
  147. }
  148. const req = {
  149. resources,
  150. rootResourcePath,
  151. }
  152. try {
  153. const options = await fsPromises.readFile(
  154. `${baseDirectory}/${directory}/options.json`
  155. )
  156. req.options = JSON.parse(options)
  157. } catch (error) {
  158. // noop
  159. }
  160. return await compile(projectId, req)
  161. }
  162. function smokeTest() {
  163. return fetchString(`${host}/smoke_test_force`, {
  164. method: 'GET',
  165. })
  166. }
  167. export default {
  168. randomId,
  169. compile,
  170. stopCompile,
  171. clearCache,
  172. getOutputFile,
  173. smokeTest,
  174. runFakeFilestoreService,
  175. startFakeFilestoreApp,
  176. syncFromCode,
  177. syncFromCodeWithImage,
  178. syncFromPdf,
  179. syncFromPdfWithImage,
  180. compileDirectory,
  181. wordcount,
  182. wordcountWithImage,
  183. }