From 037bda30c6c52aeb37f8b6bdae06a4fe0bf8fe22 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Tue, 5 May 2026 11:49:02 +0800 Subject: [PATCH] fix: download output files via CDN with required query params (fixes #369) --- src/api/base.ts | 43 ++++++++++++++++++++++++++-- src/core/remoteFileSystemProvider.ts | 10 ++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/api/base.ts b/src/api/base.ts index f81f1d76..55470237 100644 --- a/src/api/base.ts +++ b/src/api/base.ts @@ -21,6 +21,8 @@ export interface NewProjectResponseSchema { export interface CompileResponseSchema { status: 'success' | 'failure' | 'error'; compileGroup: string; + clsiServerId?: string; + pdfDownloadDomain?: string; outputFiles: Array; stats: { "latexmk-errors":number, "pdf-size":number, @@ -684,9 +686,20 @@ export class BaseAPI { return this.request('POST', `project/${projectId}/settings`, setting); } - async getFileFromClsi(identity:Identity, url:string, compileGroup:string) { - url = url.replace(/^\/+/g, ''); + async getFileFromClsi(identity:Identity, url:string, compileGroup:string, clsiServerId?:string, pdfDownloadDomain?:string) { + // If we have a CDN download domain, construct the full URL with required query params. + // The CDN is cross-origin, so we must NOT send web frontend cookies. + if (pdfDownloadDomain && clsiServerId) { + const cdnUrl = `${pdfDownloadDomain.replace(/\/+$/, '')}/${url.replace(/^\/+/g, '')}` + + `?compileGroup=${encodeURIComponent(compileGroup)}` + + `&clsiserverid=${encodeURIComponent(clsiServerId)}` + + `&enable_pdf_caching=true`; + const content = await this._downloadAbsolute(cdnUrl, false); + return { type: 'success', content: new Uint8Array(content) }; + } + // Fallback: download from web frontend (legacy path) + url = url.replace(/^\/+/g, ''); this.setIdentity(identity); const content = await this.download(url); return { @@ -695,6 +708,32 @@ export class BaseAPI { }; } + /** Download from an absolute URL, optionally including web frontend cookies. */ + private async _downloadAbsolute(absoluteUrl: string, includeCookies: boolean): Promise { + const headers: Record = { + 'Connection': 'keep-alive', + }; + if (includeCookies && this.identity) { + headers['Cookie'] = this.identity.cookies; + } + let content: Buffer[] = []; + while (true) { + const res = await fetch(absoluteUrl, { + method: 'GET', redirect: 'manual', agent: this.agent, + headers + }); + if (res.status === 200) { + content.push(await res.buffer()); + break; + } else if (res.status === 206) { + content.push(await res.buffer()); + } else { + break; + } + } + return Buffer.concat(content); + } + async proxySyncPdf(identity:Identity, projectId:string, page:number, h:number, v:number, buildId:string) { this.setIdentity(identity); const request = `project/${projectId}/sync/pdf?page=${page}&h=${h.toFixed(2)}&v=${v.toFixed(2)}&editorId=${uuidv4()}&buildId=${buildId}`; diff --git a/src/core/remoteFileSystemProvider.ts b/src/core/remoteFileSystemProvider.ts index 176e8ac5..7c0a8eec 100644 --- a/src/core/remoteFileSystemProvider.ts +++ b/src/core/remoteFileSystemProvider.ts @@ -118,6 +118,9 @@ export class VirtualFileSystem extends vscode.Disposable { private initializing?: Promise; private retryConnection: number = 0; private outputBuildId?: string; + private compileGroup?: string; + private clsiServerId?: string; + private pdfDownloadDomain?: string; private notify: (events:vscode.FileChangeEvent[])=>void; private clientManagerItem?: {manager: ClientManager, triggers: vscode.Disposable[]}; private scmCollectionItem?: {collection: SCMCollectionProvider, triggers: vscode.Disposable[]}; @@ -531,9 +534,10 @@ export class VirtualFileSystem extends vscode.Disposable { return new TextEncoder().encode(content); } } else if (fileType==='outputs') { + const {compileGroup, clsiServerId, pdfDownloadDomain} = this; return GlobalStateManager.authenticate(this.context, this.serverName) .then((identity) => { - return this.api.getFileFromClsi(identity, (fileEntity as OutputFileEntity).url, 'standard') + return this.api.getFileFromClsi(identity, (fileEntity as OutputFileEntity).url, compileGroup || 'standard', clsiServerId, pdfDownloadDomain) .then((res) => { if (res.type==='success') { EventBus.fire('fileWillOpenEvent', {uri}); @@ -896,6 +900,10 @@ export class VirtualFileSystem extends vscode.Disposable { } const res = await this.api.compile(identity, this.projectId, rootResourcePath, draft, stopOnFirstError); if (res.type==='success' && res.compile?.status==='success') { + // Store CDN download info from the response for subsequent output file requests + this.compileGroup = res.compile.compileGroup; + this.clsiServerId = res.compile.clsiServerId; + this.pdfDownloadDomain = res.compile.pdfDownloadDomain; this.updateOutputs(res.compile.outputFiles); return true; } else {