mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-03 06:09:02 +02:00
Merge pull request #28264 from overleaf/jpa-synctex
[web] use standard request handling for SyncTeX requests GitOrigin-RevId: ad5ba1834241d5939675f2533940ade741fc5abf
This commit is contained in:
@@ -818,9 +818,9 @@ function _finaliseRequest(projectId, options, project, docs, files) {
|
||||
}
|
||||
}
|
||||
|
||||
async function wordCount(projectId, userId, file, options, clsiserverid) {
|
||||
const { compileBackendClass, compileGroup } = options
|
||||
const req = await _buildRequest(projectId, options)
|
||||
async function wordCount(projectId, userId, file, limits, clsiserverid) {
|
||||
const { compileBackendClass, compileGroup } = limits
|
||||
const req = await _buildRequest(projectId, limits)
|
||||
const filename = file || req.compile.rootResourcePath
|
||||
const url = _getCompilerUrl(
|
||||
compileBackendClass,
|
||||
@@ -847,6 +847,56 @@ async function wordCount(projectId, userId, file, options, clsiserverid) {
|
||||
return body
|
||||
}
|
||||
|
||||
async function syncTeX(
|
||||
projectId,
|
||||
userId,
|
||||
{
|
||||
direction,
|
||||
compileFromClsiCache,
|
||||
limits,
|
||||
imageName,
|
||||
validatedOptions,
|
||||
clsiServerId,
|
||||
}
|
||||
) {
|
||||
const { compileBackendClass, compileGroup } = limits
|
||||
const url = _getCompilerUrl(
|
||||
compileBackendClass,
|
||||
compileGroup,
|
||||
projectId,
|
||||
userId,
|
||||
`sync/${direction}`
|
||||
)
|
||||
url.searchParams.set(
|
||||
'compileFromClsiCache',
|
||||
compileFromClsiCache && ['alpha', 'priority'].includes(compileGroup)
|
||||
)
|
||||
url.searchParams.set('imageName', imageName)
|
||||
for (const [key, value] of Object.entries(validatedOptions)) {
|
||||
url.searchParams.set(key, value)
|
||||
}
|
||||
const opts = {
|
||||
method: 'GET',
|
||||
}
|
||||
try {
|
||||
const { body } = await _makeRequestWithClsiServerId(
|
||||
projectId,
|
||||
userId,
|
||||
compileGroup,
|
||||
compileBackendClass,
|
||||
url,
|
||||
opts,
|
||||
clsiServerId
|
||||
)
|
||||
return body
|
||||
} catch (err) {
|
||||
if (err instanceof RequestFailedError && err.response.status === 404) {
|
||||
throw new Errors.NotFoundError()
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function _getClsiServerIdFromResponse(response) {
|
||||
const setCookieHeaders = response.headers.raw()['set-cookie'] ?? []
|
||||
for (const header of setCookieHeaders) {
|
||||
@@ -883,6 +933,7 @@ module.exports = {
|
||||
deleteAuxFiles: callbackify(deleteAuxFiles),
|
||||
getOutputFileStream: callbackify(getOutputFileStream),
|
||||
wordCount: callbackify(wordCount),
|
||||
syncTeX: callbackify(syncTeX),
|
||||
promises: {
|
||||
sendRequest,
|
||||
sendExternalRequest,
|
||||
@@ -890,5 +941,6 @@ module.exports = {
|
||||
deleteAuxFiles,
|
||||
getOutputFileStream,
|
||||
wordCount,
|
||||
syncTeX,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const CompileManager = require('./CompileManager')
|
||||
const ClsiManager = require('./ClsiManager')
|
||||
const logger = require('@overleaf/logger')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const Errors = require('../Errors/Errors')
|
||||
const SessionManager = require('../Authentication/SessionManager')
|
||||
const { RateLimiter } = require('../../infrastructure/RateLimiter')
|
||||
const ClsiCookieManager = require('./ClsiCookieManager')(
|
||||
@@ -38,16 +39,6 @@ function getOutputFilesArchiveSpecification(projectId, userId, buildId) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getImageNameForProject(projectId) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
imageName: 1,
|
||||
})
|
||||
if (!project) {
|
||||
throw new Error('project not found')
|
||||
}
|
||||
return project.imageName
|
||||
}
|
||||
|
||||
async function getPdfCachingMinChunkSize(req, res) {
|
||||
const { variant } = await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
@@ -121,6 +112,32 @@ async function _getSplitTestOptions(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
async function _syncTeX(req, res, direction, validatedOptions) {
|
||||
const projectId = req.params.Project_id
|
||||
const { editorId, buildId, clsiserverid: clsiServerId } = req.query
|
||||
if (!editorId?.match(/^[a-f0-9-]+$/)) throw new Error('invalid ?editorId')
|
||||
if (!buildId?.match(/^[a-f0-9-]+$/)) throw new Error('invalid ?buildId')
|
||||
|
||||
const userId = CompileController._getUserIdForCompile(req)
|
||||
const { compileFromClsiCache } = await _getSplitTestOptions(req, res)
|
||||
try {
|
||||
const body = await CompileManager.promises.syncTeX(projectId, userId, {
|
||||
direction,
|
||||
compileFromClsiCache,
|
||||
validatedOptions: {
|
||||
...validatedOptions,
|
||||
editorId,
|
||||
buildId,
|
||||
},
|
||||
clsiServerId,
|
||||
})
|
||||
res.json(body)
|
||||
} catch (err) {
|
||||
if (err instanceof Errors.NotFoundError) return res.status(404).end()
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const _CompileController = {
|
||||
async compile(req, res) {
|
||||
res.setTimeout(COMPILE_TIMEOUT_MS)
|
||||
@@ -470,18 +487,8 @@ const _CompileController = {
|
||||
return url
|
||||
},
|
||||
|
||||
// compute a POST url for a project, user (optional) and action
|
||||
_getUrl(projectId, userId, action) {
|
||||
let path = `/project/${projectId}`
|
||||
if (userId != null) {
|
||||
path += `/user/${userId}`
|
||||
}
|
||||
return `${path}/${action}`
|
||||
},
|
||||
|
||||
async proxySyncPdf(req, res) {
|
||||
const projectId = req.params.Project_id
|
||||
const { page, h, v, editorId, buildId } = req.query
|
||||
const { page, h, v } = req.query
|
||||
if (!page?.match(/^\d+$/)) {
|
||||
throw new Error('invalid page parameter')
|
||||
}
|
||||
@@ -491,28 +498,11 @@ const _CompileController = {
|
||||
if (!v?.match(/^-?\d+\.\d+$/)) {
|
||||
throw new Error('invalid v parameter')
|
||||
}
|
||||
// whether this request is going to a per-user container
|
||||
const userId = CompileController._getUserIdForCompile(req)
|
||||
|
||||
const imageName = await getImageNameForProject(projectId)
|
||||
|
||||
const { compileFromClsiCache } = await _getSplitTestOptions(req, res)
|
||||
|
||||
const url = _CompileController._getUrl(projectId, userId, 'sync/pdf')
|
||||
|
||||
await CompileController._proxyToClsi(
|
||||
projectId,
|
||||
'sync-to-pdf',
|
||||
url,
|
||||
{ page, h, v, imageName, editorId, buildId, compileFromClsiCache },
|
||||
req,
|
||||
res
|
||||
)
|
||||
await _syncTeX(req, res, 'pdf', { page, h, v })
|
||||
},
|
||||
|
||||
async proxySyncCode(req, res) {
|
||||
const projectId = req.params.Project_id
|
||||
const { file, line, column, editorId, buildId } = req.query
|
||||
const { file, line, column } = req.query
|
||||
if (file == null) {
|
||||
throw new Error('missing file parameter')
|
||||
}
|
||||
@@ -531,40 +521,12 @@ const _CompileController = {
|
||||
if (!column?.match(/^\d+$/)) {
|
||||
throw new Error('invalid column parameter')
|
||||
}
|
||||
const userId = CompileController._getUserIdForCompile(req)
|
||||
|
||||
const imageName = await getImageNameForProject(projectId)
|
||||
|
||||
const { compileFromClsiCache } = await _getSplitTestOptions(req, res)
|
||||
|
||||
const url = _CompileController._getUrl(projectId, userId, 'sync/code')
|
||||
await CompileController._proxyToClsi(
|
||||
projectId,
|
||||
'sync-to-code',
|
||||
url,
|
||||
{
|
||||
file,
|
||||
line,
|
||||
column,
|
||||
imageName,
|
||||
editorId,
|
||||
buildId,
|
||||
compileFromClsiCache,
|
||||
},
|
||||
req,
|
||||
res
|
||||
)
|
||||
await _syncTeX(req, res, 'code', { file, line, column })
|
||||
},
|
||||
|
||||
async _proxyToClsi(projectId, action, url, qs, req, res) {
|
||||
const limits =
|
||||
await CompileManager.promises.getProjectCompileLimits(projectId)
|
||||
if (
|
||||
qs?.compileFromClsiCache &&
|
||||
!['alpha', 'priority'].includes(limits.compileGroup)
|
||||
) {
|
||||
qs.compileFromClsiCache = false
|
||||
}
|
||||
return CompileController._proxyToClsiWithLimits(
|
||||
projectId,
|
||||
action,
|
||||
|
||||
@@ -110,7 +110,13 @@ async function getProjectCompileLimits(projectId) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
owner_ref: 1,
|
||||
})
|
||||
return _getProjectCompileLimits(project)
|
||||
}
|
||||
|
||||
async function _getProjectCompileLimits(project) {
|
||||
if (!project) {
|
||||
throw new Error('project not found')
|
||||
}
|
||||
const owner = await UserGetter.promises.getUser(project.owner_ref, {
|
||||
_id: 1,
|
||||
alphaProgram: 1,
|
||||
@@ -162,6 +168,27 @@ async function wordCount(projectId, userId, file, clsiserverid) {
|
||||
)
|
||||
}
|
||||
|
||||
async function syncTeX(
|
||||
projectId,
|
||||
userId,
|
||||
{ direction, compileFromClsiCache, validatedOptions, clsiServerId }
|
||||
) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
owner_ref: 1,
|
||||
imageName: 1,
|
||||
})
|
||||
const limits = await _getProjectCompileLimits(project)
|
||||
const { imageName } = project
|
||||
return await ClsiManager.promises.syncTeX(projectId, userId, {
|
||||
direction,
|
||||
limits,
|
||||
imageName,
|
||||
compileFromClsiCache,
|
||||
validatedOptions,
|
||||
clsiServerId,
|
||||
})
|
||||
}
|
||||
|
||||
async function stopCompile(projectId, userId) {
|
||||
const limits =
|
||||
await CompileManager.promises.getProjectCompileLimits(projectId)
|
||||
@@ -188,6 +215,7 @@ module.exports = CompileManager = {
|
||||
getProjectCompileLimits,
|
||||
stopCompile,
|
||||
wordCount,
|
||||
syncTeX,
|
||||
},
|
||||
compile: callbackifyMultiResult(instrumentedCompile, [
|
||||
'status',
|
||||
@@ -249,6 +277,7 @@ module.exports = CompileManager = {
|
||||
},
|
||||
|
||||
wordCount: callbackify(wordCount),
|
||||
syncTeX: callbackify(syncTeX),
|
||||
}
|
||||
|
||||
const autoCompileRateLimiters = new Map()
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('CompileController', function () {
|
||||
promises: {
|
||||
compile: sinon.stub(),
|
||||
getProjectCompileLimits: sinon.stub(),
|
||||
syncTeX: sinon.stub(),
|
||||
},
|
||||
}
|
||||
this.ClsiManager = {
|
||||
@@ -594,16 +595,24 @@ describe('CompileController', function () {
|
||||
})
|
||||
})
|
||||
describe('proxySyncCode', function () {
|
||||
let file, line, column, imageName, editorId, buildId
|
||||
let file, line, column, imageName, editorId, buildId, clsiServerId
|
||||
|
||||
beforeEach(async function () {
|
||||
this.req.params = { Project_id: this.projectId }
|
||||
clsiServerId = 'clsi-1'
|
||||
file = 'main.tex'
|
||||
line = String(Date.now())
|
||||
column = String(Date.now() + 1)
|
||||
editorId = '172977cb-361e-4854-a4dc-a71cf11512e5'
|
||||
buildId = '195b4a3f9e7-03e5be430a9e7796'
|
||||
this.req.query = { file, line, column, editorId, buildId }
|
||||
this.req.query = {
|
||||
file,
|
||||
line,
|
||||
column,
|
||||
editorId,
|
||||
buildId,
|
||||
clsiserverid: clsiServerId,
|
||||
}
|
||||
|
||||
imageName = 'foo/bar:tag-0'
|
||||
this.ProjectGetter.promises.getProject = sinon
|
||||
@@ -615,37 +624,45 @@ describe('CompileController', function () {
|
||||
await this.CompileController.proxySyncCode(this.req, this.res, this.next)
|
||||
})
|
||||
|
||||
it('should proxy the request with an imageName', function () {
|
||||
expect(this.CompileController._proxyToClsi).to.have.been.calledWith(
|
||||
it('should parse the parameters', function () {
|
||||
expect(this.CompileManager.promises.syncTeX).to.have.been.calledWith(
|
||||
this.projectId,
|
||||
'sync-to-code',
|
||||
`/project/${this.projectId}/user/${this.user_id}/sync/code`,
|
||||
this.user_id,
|
||||
{
|
||||
file,
|
||||
line,
|
||||
column,
|
||||
imageName,
|
||||
editorId,
|
||||
buildId,
|
||||
direction: 'code',
|
||||
compileFromClsiCache: false,
|
||||
},
|
||||
this.req,
|
||||
this.res
|
||||
validatedOptions: {
|
||||
file,
|
||||
line,
|
||||
column,
|
||||
editorId,
|
||||
buildId,
|
||||
},
|
||||
clsiServerId,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('proxySyncPdf', function () {
|
||||
let page, h, v, imageName, editorId, buildId
|
||||
let page, h, v, imageName, editorId, buildId, clsiServerId
|
||||
|
||||
beforeEach(async function () {
|
||||
this.req.params = { Project_id: this.projectId }
|
||||
clsiServerId = 'clsi-1'
|
||||
page = String(Date.now())
|
||||
h = String(Math.random())
|
||||
v = String(Math.random())
|
||||
editorId = '172977cb-361e-4854-a4dc-a71cf11512e5'
|
||||
buildId = '195b4a3f9e7-03e5be430a9e7796'
|
||||
this.req.query = { page, h, v, editorId, buildId }
|
||||
this.req.query = {
|
||||
page,
|
||||
h,
|
||||
v,
|
||||
editorId,
|
||||
buildId,
|
||||
clsiserverid: clsiServerId,
|
||||
}
|
||||
|
||||
imageName = 'foo/bar:tag-1'
|
||||
this.ProjectGetter.promises.getProject = sinon
|
||||
@@ -657,22 +674,22 @@ describe('CompileController', function () {
|
||||
await this.CompileController.proxySyncPdf(this.req, this.res, this.next)
|
||||
})
|
||||
|
||||
it('should proxy the request with an imageName', function () {
|
||||
expect(this.CompileController._proxyToClsi).to.have.been.calledWith(
|
||||
it('should parse the parameters', function () {
|
||||
expect(this.CompileManager.promises.syncTeX).to.have.been.calledWith(
|
||||
this.projectId,
|
||||
'sync-to-pdf',
|
||||
`/project/${this.projectId}/user/${this.user_id}/sync/pdf`,
|
||||
this.user_id,
|
||||
{
|
||||
page,
|
||||
h,
|
||||
v,
|
||||
imageName,
|
||||
editorId,
|
||||
buildId,
|
||||
direction: 'pdf',
|
||||
compileFromClsiCache: false,
|
||||
},
|
||||
this.req,
|
||||
this.res
|
||||
validatedOptions: {
|
||||
page,
|
||||
h,
|
||||
v,
|
||||
editorId,
|
||||
buildId,
|
||||
},
|
||||
clsiServerId,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user