Client.js 5.0 KB

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