diff --git a/docs/self-hosting/docker.md b/docs/self-hosting/docker.md index 1c33236..0130afd 100644 --- a/docs/self-hosting/docker.md +++ b/docs/self-hosting/docker.md @@ -2,6 +2,15 @@ The easiest way to self-host BentoPDF in a production environment. +> [!IMPORTANT] +> **Required Headers for Office File Conversion** +> +> LibreOffice-based tools (Word, Excel, PowerPoint conversion) require these HTTP headers for `SharedArrayBuffer` support: +> - `Cross-Origin-Opener-Policy: same-origin` +> - `Cross-Origin-Embedder-Policy: require-corp` +> +> The official Docker images include these headers. If using a reverse proxy (Traefik, Caddy, etc.), ensure these headers are preserved or added. + ## Quick Start ```bash @@ -106,6 +115,10 @@ services: - "traefik.http.routers.bentopdf.entrypoints=websecure" - "traefik.http.routers.bentopdf.tls.certresolver=letsencrypt" - "traefik.http.services.bentopdf.loadbalancer.server.port=8080" + # Required headers for SharedArrayBuffer (LibreOffice WASM) + - "traefik.http.routers.bentopdf.middlewares=bentopdf-headers" + - "traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Opener-Policy=same-origin" + - "traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Embedder-Policy=require-corp" restart: unless-stopped ``` @@ -135,6 +148,8 @@ Caddyfile: ``` pdf.example.com { reverse_proxy bentopdf:8080 + header Cross-Origin-Opener-Policy "same-origin" + header Cross-Origin-Embedder-Policy "require-corp" } ``` diff --git a/nginx.conf b/nginx.conf index 56fab7a..8af4a3b 100644 --- a/nginx.conf +++ b/nginx.conf @@ -96,6 +96,7 @@ http { add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Cross-Origin-Opener-Policy "same-origin" always; + add_header Cross-Origin-Embedder-Policy "require-corp" always; add_header Cross-Origin-Resource-Policy "cross-origin" always; } } diff --git a/src/js/utils/libreoffice-loader.ts b/src/js/utils/libreoffice-loader.ts index a0083d3..f6b7718 100644 --- a/src/js/utils/libreoffice-loader.ts +++ b/src/js/utils/libreoffice-loader.ts @@ -6,7 +6,7 @@ */ import { WorkerBrowserConverter } from '@matbee/libreoffice-converter/browser'; -import { getWasmBaseUrl } from '../config/wasm-cdn-config.js'; +import { getWasmBaseUrl, getWasmFallbackUrl, isCdnEnabled } from '../config/wasm-cdn-config.js'; export interface LoadProgress { phase: 'loading' | 'initializing' | 'converting' | 'complete' | 'ready'; @@ -24,10 +24,12 @@ export class LibreOfficeConverter { private initialized = false; private initializing = false; private basePath: string; + private localPath: string; constructor(basePath?: string) { // Use CDN if available, otherwise use provided basePath or default local path this.basePath = basePath || getWasmBaseUrl('libreoffice'); + this.localPath = getWasmFallbackUrl('libreoffice'); } async initialize(onProgress?: ProgressCallback): Promise { @@ -47,12 +49,14 @@ export class LibreOfficeConverter { try { progressCallback?.({ phase: 'loading', percent: 0, message: 'Loading conversion engine...' }); + const workerPath = isCdnEnabled() ? this.localPath : this.basePath; + this.converter = new WorkerBrowserConverter({ sofficeJs: `${this.basePath}soffice.js`, sofficeWasm: `${this.basePath}soffice.wasm.gz`, sofficeData: `${this.basePath}soffice.data.gz`, - sofficeWorkerJs: `${this.basePath}soffice.worker.js`, - browserWorkerJs: `${this.basePath}browser.worker.global.js`, + sofficeWorkerJs: `${workerPath}soffice.worker.js`, + browserWorkerJs: `${workerPath}browser.worker.global.js`, verbose: false, onProgress: (info: { phase: string; percent: number; message: string }) => { if (progressCallback && !this.initialized) {