fix(astrolabe): load pdfjs-dist externally to fix PDF viewer
When viewing PDF chunks in semantic search, the PDF viewer failed with "can't access private field" errors. This was caused by: 1. CSP blocks web workers (worker-src 'none'), forcing fake worker mode 2. Vite transforms ES private fields in the bundle, but the worker file is untransformed, causing incompatible private field implementations 3. Vue's ref() wraps PDFDocumentProxy in a Proxy, which can't access ES private fields Fixed by: - Loading pdfjs-dist externally via script tag (avoids Vite transform) - Creating pdfjs-loader.mjs that imports pdf.mjs and sets window.pdfjsLib - Using Util::addScript() for CSP-compliant script loading with nonces - Using shallowRef() instead of ref() for pdfDoc to avoid Proxy wrapper - Setting workerSrc at runtime using OC.linkTo() for correct app path Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Vendored
-12
@@ -394,18 +394,6 @@ import MarkdownViewer from './components/MarkdownViewer.vue'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import Plotly from 'plotly.js-dist-min'
|
||||
import * as pdfjsLib from 'pdfjs-dist'
|
||||
|
||||
// Set worker source with error handling
|
||||
try {
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.mjs',
|
||||
import.meta.url,
|
||||
).toString()
|
||||
} catch (e) {
|
||||
console.warn('Failed to set PDF.js worker, will use fallback', e)
|
||||
// PDF.js will use fake worker automatically
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
|
||||
+12
-3
@@ -15,11 +15,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import * as pdfjsLib from 'pdfjs-dist'
|
||||
import { ref, shallowRef, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { NcLoadingIcon } from '@nextcloud/vue'
|
||||
|
||||
// Use global pdfjsLib loaded by pdfjs-loader.mjs (external, not bundled)
|
||||
// This avoids Vite transforming ES private fields which breaks fake worker compatibility
|
||||
const pdfjsLib = window.pdfjsLib
|
||||
import AlertCircle from 'vue-material-design-icons/AlertCircle.vue'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -40,7 +43,9 @@ const props = defineProps({
|
||||
const emit = defineEmits(['loaded', 'error', 'page-rendered'])
|
||||
|
||||
// Reactive state
|
||||
const pdfDoc = ref(null)
|
||||
// Use shallowRef for pdfDoc because PDFDocumentProxy uses ES private fields
|
||||
// which can't be accessed through Vue's Proxy wrapper
|
||||
const pdfDoc = shallowRef(null)
|
||||
const loading = ref(true)
|
||||
const error = ref(null)
|
||||
const totalPages = ref(0)
|
||||
@@ -60,6 +65,10 @@ async function loadPDF() {
|
||||
const encodedPath = cleanPath.split('/').map(encodeURIComponent).join('/')
|
||||
const downloadUrl = generateUrl(`/remote.php/webdav/${encodedPath}`)
|
||||
|
||||
// Set worker source using OC.linkTo for correct app webroot path
|
||||
// Must be done here (not at module load time) because _oc_appswebroots isn't populated until after page load
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = window.OC.linkTo('astrolabe', 'js/pdf.worker.mjs')
|
||||
|
||||
// Load PDF document
|
||||
const loadingTask = pdfjsLib.getDocument({
|
||||
url: downloadUrl,
|
||||
|
||||
+7
-2
@@ -2,10 +2,15 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use OCA\Astrolabe\AppInfo\Application;
|
||||
use OCP\Util;
|
||||
|
||||
Util::addScript(OCA\Astrolabe\AppInfo\Application::APP_ID, OCA\Astrolabe\AppInfo\Application::APP_ID . '-main');
|
||||
Util::addStyle(OCA\Astrolabe\AppInfo\Application::APP_ID, OCA\Astrolabe\AppInfo\Application::APP_ID . '-main');
|
||||
// Load PDF.js loader first (must be external, not bundled by Vite,
|
||||
// to avoid ES private field transformation issues with fake worker fallback)
|
||||
// The loader imports pdf.mjs and sets window.pdfjsLib before the main app runs
|
||||
Util::addScript(Application::APP_ID, 'pdfjs-loader');
|
||||
Util::addScript(Application::APP_ID, Application::APP_ID . '-main');
|
||||
Util::addStyle(Application::APP_ID, Application::APP_ID . '-main');
|
||||
|
||||
?>
|
||||
|
||||
|
||||
Vendored
+35
-2
@@ -1,7 +1,40 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { resolve } from 'path'
|
||||
import { readFileSync } from 'fs'
|
||||
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/')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read app info from info.xml for @nextcloud/vue
|
||||
const infoXml = readFileSync(resolve(__dirname, 'appinfo/info.xml'), 'utf-8')
|
||||
@@ -9,7 +42,7 @@ const appName = infoXml.match(/<id>([^<]+)<\/id>/)?.[1] || 'astrolabe'
|
||||
const appVersion = infoXml.match(/<version>([^<]+)<\/version>/)?.[1] || ''
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
plugins: [vue(), copyPdfFiles()],
|
||||
define: {
|
||||
appName: JSON.stringify(appName),
|
||||
appVersion: JSON.stringify(appVersion),
|
||||
|
||||
Reference in New Issue
Block a user