refactor(astrolabe): replace client-side PDF.js with server-side PyMuPDF rendering

Replace the client-side PDF.js viewer with server-side rendering using PyMuPDF.
This avoids CSP worker restrictions and ES private field access issues that
affected Chromium browsers.

Changes:
- Add /api/v1/pdf-preview endpoint to MCP server (management.py)
- Add pdf-preview route and controller action in Astrolabe PHP backend
- Refactor PDFViewer.vue to display server-rendered PNG images
- Remove pdfjs-dist dependency and client-side PDF loading code
- Use @nextcloud/axios for CSRF token handling in PDFViewer

The server downloads the PDF via WebDAV, renders the requested page with
PyMuPDF at the specified scale, and returns a base64-encoded PNG image.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-01-26 20:04:57 +01:00
co-authored by Claude Opus 4.5
parent bc62f2a066
commit d5544a7731
9 changed files with 325 additions and 162 deletions
+2 -35
View File
@@ -1,40 +1,7 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { readFileSync, copyFileSync, writeFileSync, mkdirSync } from 'fs'
// Plugin to copy PDF.js files to output directory
// Both pdf.mjs and pdf.worker.mjs are loaded externally to avoid Vite transforming
// ES private fields, which breaks compatibility with the fake worker fallback
function copyPdfFiles() {
return {
name: 'copy-pdf-files',
writeBundle() {
mkdirSync(resolve(__dirname, 'js'), { recursive: true })
// Copy main library
copyFileSync(
resolve(__dirname, 'node_modules/pdfjs-dist/build/pdf.mjs'),
resolve(__dirname, 'js/pdf.mjs')
)
console.log('Copied pdf.mjs to js/')
// Copy worker (loaded by pdfjs at runtime)
copyFileSync(
resolve(__dirname, 'node_modules/pdfjs-dist/build/pdf.worker.mjs'),
resolve(__dirname, 'js/pdf.worker.mjs')
)
console.log('Copied pdf.worker.mjs to js/')
// Create loader script that imports pdf.mjs and sets window.pdfjsLib
// This is loaded via script tag before the main app
const loaderScript = `// PDF.js loader - imports pdf.mjs and exposes it globally
// Loaded before main app to make pdfjsLib available as window.pdfjsLib
import * as pdfjsLib from './pdf.mjs';
window.pdfjsLib = pdfjsLib;
`
writeFileSync(resolve(__dirname, 'js/pdfjs-loader.mjs'), loaderScript)
console.log('Created pdfjs-loader.mjs in js/')
}
}
}
import { readFileSync } from 'fs'
// Read app info from info.xml for @nextcloud/vue
const infoXml = readFileSync(resolve(__dirname, 'appinfo/info.xml'), 'utf-8')
@@ -42,7 +9,7 @@ const appName = infoXml.match(/<id>([^<]+)<\/id>/)?.[1] || 'astrolabe'
const appVersion = infoXml.match(/<version>([^<]+)<\/version>/)?.[1] || ''
export default defineConfig({
plugins: [vue(), copyPdfFiles()],
plugins: [vue()],
define: {
appName: JSON.stringify(appName),
appVersion: JSON.stringify(appVersion),