From 612f6f9a2d9b57cdc60aaac03ee8bee207618f8a Mon Sep 17 00:00:00 2001 From: alam00000 Date: Mon, 2 Feb 2026 15:14:30 +0530 Subject: [PATCH] feat: add WASM module configuration with pre-configured CDN URLs and update settings UI --- .env.example | 13 +++ Dockerfile | 9 ++ README.md | 141 +++++++++++++++++++++++++---- cloudflare/WASM-PROXY.md | 12 +++ docs/licensing.md | 24 ++--- docs/self-hosting/docker.md | 53 ++++++++++- docs/self-hosting/index.md | 115 ++++++++++++++++++++--- src/js/logic/wasm-settings-page.ts | 50 ++++++++-- src/js/utils/wasm-provider.ts | 41 ++++++++- src/pages/wasm-settings.html | 18 ++-- 10 files changed, 404 insertions(+), 72 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..dc77b4a --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# BentoPDF Environment Variables +# Copy this file to .env.production and configure as needed. + +# CORS Proxy for digital signature certificate chain fetching +VITE_CORS_PROXY_URL= +VITE_CORS_PROXY_SECRET= + +# WASM Module URLs +# Pre-configured defaults enable advanced PDF features out of the box. +# For air-gapped / offline deployments, point these to your internal server (e.g., /wasm/pymupdf/). +VITE_WASM_PYMUPDF_URL=https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.14/ +VITE_WASM_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/ +VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/coherentpdf/dist/ diff --git a/Dockerfile b/Dockerfile index e129421..5d669d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,15 @@ ENV COMPRESSION_MODE=$COMPRESSION_MODE ARG BASE_URL ENV BASE_URL=$BASE_URL +# WASM module URLs (pre-configured defaults) +# Override these for air-gapped or self-hosted WASM deployments +ARG VITE_WASM_PYMUPDF_URL +ARG VITE_WASM_GS_URL +ARG VITE_WASM_CPDF_URL +ENV VITE_WASM_PYMUPDF_URL=$VITE_WASM_PYMUPDF_URL +ENV VITE_WASM_GS_URL=$VITE_WASM_GS_URL +ENV VITE_WASM_CPDF_URL=$VITE_WASM_CPDF_URL + ENV NODE_OPTIONS="--max-old-space-size=3072" RUN npm run build:with-docs diff --git a/README.md b/README.md index 3b583e2..743019c 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ - [Docker Compose / Podman Compose](#-run-with-docker-compose--podman-compose-recommended) - [Podman Quadlet](#-podman-quadlet-systemd-integration) - [Simple Mode](#-simple-mode-for-internal-use) + - [WASM Configuration](#wasm-configuration) + - [Air-Gapped / Offline Deployment](#air-gapped--offline-deployment) - [Security Features](#-security-features) - [Digital Signature CORS Proxy](#digital-signature-cors-proxy-required) - [Version Management](#-version-management) @@ -90,9 +92,9 @@ BentoPDF is **dual-licensed** to fit your needs: 📖 For more details, see our [Licensing Page](https://bentopdf.com/licensing.html) -### AGPL Components (Not Bundled) +### AGPL Components (Pre-configured via CDN) -BentoPDF does **not** bundle AGPL-licensed processing libraries. The following components must be configured separately via **Advanced Settings** if you wish to use their features: +BentoPDF does **not** bundle AGPL-licensed processing libraries in its source code, but **pre-configures CDN URLs** so all features work out of the box with zero setup: | Component | License | Features Enabled | | ---------------------- | -------- | --------------------------------------------------------------------------------------------------- | @@ -100,13 +102,8 @@ BentoPDF does **not** bundle AGPL-licensed processing libraries. The following c | **Ghostscript** | AGPL-3.0 | PDF/A Conversion, Font to Outline | | **CoherentPDF (CPDF)** | AGPL-3.0 | Merge, Split by Bookmarks, Table of Contents, PDF to/from JSON, Attachments | -> **Why?** This separation ensures clear legal boundaries. Users who need these features can configure their own WASM sources or use our optional [WASM Proxy](cloudflare/WASM-PROXY.md) to load them from external URLs. - -**To enable these features:** - -1. Navigate to **Advanced Settings** in BentoPDF -2. Configure the URL for each WASM module you need -3. The modules will be loaded dynamically when required +> [!TIP] +> **Zero-config by default.** WASM modules are loaded at runtime from jsDelivr CDN. No manual configuration is needed. For custom deployments (air-gapped, self-hosted), see [WASM Configuration](#wasm-configuration) below.
@@ -335,7 +332,8 @@ podman run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest podman run -p 3000:8080 docker.io/bentopdfteam/bentopdf:latest ``` -> **Note:** All `docker` commands in this documentation work with Podman by replacing `docker` with `podman`. +> [!NOTE] +> All `docker` commands in this documentation work with Podman by replacing `docker` with `podman`. @@ -368,7 +366,8 @@ npx http-server -c-1 The website will be accessible at: `http://localhost:8080/` -> **Note:** The `-c-1` flag disables caching for development. +> [!NOTE] +> The `-c-1` flag disables caching for development. **Build from Source (Advanced):** @@ -442,6 +441,110 @@ npm run build - Local files are **always included** as automatic fallback - If CDN fails then it falls back to local files +

⚙️ WASM Configuration

+ +Advanced PDF features (PyMuPDF, Ghostscript, CoherentPDF) are pre-configured to load from jsDelivr CDN via environment variables. This means **all features work out of the box** — no manual setup needed. + +The default URLs are set in `.env.production`: + +```bash +VITE_WASM_PYMUPDF_URL=https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.14/ +VITE_WASM_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/ +VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/coherentpdf/dist/ +``` + +To override via Docker build args: + +```bash +docker build \ + --build-arg VITE_WASM_PYMUPDF_URL=https://your-server.com/pymupdf/ \ + --build-arg VITE_WASM_GS_URL=https://your-server.com/gs/ \ + --build-arg VITE_WASM_CPDF_URL=https://your-server.com/cpdf/ \ + -t bentopdf . +``` + +To disable a module (require manual user config via Advanced Settings), set its variable to an empty string. + +Users can also override these defaults per-browser via **Advanced Settings** in the UI — user overrides take priority over the environment defaults. + +> [!IMPORTANT] +> These URLs are baked into the JavaScript at **build time**. The WASM files themselves are downloaded by the **user's browser** at runtime — Docker does not download them during the build. + +

🔒 Air-Gapped / Offline Deployment

+ +For networks with no internet access (government, healthcare, financial, etc.), you need to prepare everything on a machine **with** internet, then transfer it into the isolated network. + +**Step 1: Download the WASM packages** (on a machine with internet) + +```bash +npm pack @bentopdf/pymupdf-wasm@0.11.14 +npm pack @bentopdf/gs-wasm +npm pack coherentpdf +``` + +This creates three `.tgz` files in your current directory. + +**Step 2: Build the Docker image with internal URLs** (on a machine with internet) + +Point the WASM URLs to where you'll host the files inside the air-gapped network: + +```bash +# Clone and build +git clone https://github.com/alam00000/bentopdf.git +cd bentopdf + +docker build \ + --build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \ + --build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \ + --build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \ + -t bentopdf . +``` + +**Step 3: Export the Docker image** + +```bash +docker save bentopdf -o bentopdf.tar +``` + +**Step 4: Transfer into the air-gapped network** + +Copy these files via USB drive, internal artifact repository, or approved transfer method: + +- `bentopdf.tar` — the Docker image +- `bentopdf-pymupdf-wasm-0.11.14.tgz` — PyMuPDF WASM package +- `bentopdf-gs-wasm-*.tgz` — Ghostscript WASM package +- `coherentpdf-*.tgz` — CoherentPDF WASM package + +**Step 5: Set up inside the air-gapped network** + +```bash +# Load the Docker image +docker load -i bentopdf.tar + +# Extract the WASM packages +mkdir -p /var/www/wasm/pymupdf /var/www/wasm/gs /var/www/wasm/cpdf +tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C /var/www/wasm/pymupdf --strip-components=1 +tar xzf bentopdf-gs-wasm-*.tgz -C /var/www/wasm/gs --strip-components=1 +tar xzf coherentpdf-*.tgz -C /var/www/wasm/cpdf --strip-components=1 + +# Serve the WASM files on your internal web server (e.g., nginx, Apache) +# Make sure they're accessible at the URLs you configured in Step 2 + +# Run BentoPDF +docker run -d -p 3000:8080 --restart unless-stopped bentopdf +``` + +Users open their browser, access BentoPDF on the internal network, and the browser fetches WASM files from the internal server. No internet required at any point. + +> [!NOTE] +> If you're building from source instead of Docker, set the variables in `.env.production` before running `npm run build`: +> +> ```bash +> VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ +> VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ +> VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ +> ``` + **Subdirectory Hosting:** BentoPDF can also be hosted from a subdirectory (e.g., `example.com/tools/bentopdf/`): @@ -493,7 +596,7 @@ docker build \ docker run -p 3000:8080 bentopdf-simple ``` -> **Important**: +> [!IMPORTANT] > > - Always include trailing slashes in `BASE_URL` (e.g., `/bentopdf/` not `/bentopdf`) > - The default value is `/` for root deployment @@ -662,7 +765,8 @@ npx wrangler deploy #### HMAC Signature Verification (Optional) -> **⚠️ Security Warning:** Client-side secrets can be extracted from bundled JavaScript. For production deployments with sensitive requirements, use your own backend server to proxy requests instead of embedding secrets in frontend code. +> [!WARNING] +> Client-side secrets can be extracted from bundled JavaScript. For production deployments with sensitive requirements, use your own backend server to proxy requests instead of embedding secrets in frontend code. BentoPDF uses client-side HMAC as a deterrent against casual abuse, but accepts this tradeoff due to its fully client-side architecture. To enable: @@ -748,7 +852,8 @@ For detailed release instructions, see [RELEASE.md](RELEASE.md). The application will be available at `http://localhost:3000`. - > **Note:** After making any local changes to the code, rebuild the Docker image using: + > [!NOTE] + > After making any local changes to the code, rebuild the Docker image using: ```bash docker-compose -f docker-compose.dev.yml up --build -d @@ -766,7 +871,8 @@ BentoPDF was originally built using **HTML**, **CSS**, and **vanilla JavaScript* - **TypeScript**: For type safety and an improved developer experience. - **Tailwind CSS**: For rapid and consistent UI development. -> **Note:** Some parts of the codebase still use legacy structures from the original implementation. Contributors should expect gradual updates as testing and refactoring continue. +> [!NOTE] +> Some parts of the codebase still use legacy structures from the original implementation. Contributors should expect gradual updates as testing and refactoring continue. --- @@ -841,12 +947,13 @@ BentoPDF wouldn't be possible without the amazing open-source tools and librarie - **[qpdf](https://github.com/qpdf/qpdf)** and **[qpdf-wasm](https://github.com/neslinesli93/qpdf-wasm)** – For inspecting, repairing, and transforming PDF files. - **[LibreOffice](https://www.libreoffice.org/)** – For powerful document conversion capabilities. -**AGPL Libraries (Not Bundled - User Configured):** +**AGPL Libraries (Pre-configured via CDN):** - **[CoherentPDF (cpdf)](https://www.coherentpdf.com/)** – For content-preserving PDF operations. _(AGPL-3.0)_ - **[PyMuPDF](https://github.com/pymupdf/PyMuPDF)** – For high-performance PDF manipulation and data extraction. _(AGPL-3.0)_ - **[Ghostscript (GhostPDL)](https://github.com/ArtifexSoftware/ghostpdl)** – For PDF/A conversion and font outlining. _(AGPL-3.0)_ -> **Note:** AGPL-licensed libraries are not bundled with BentoPDF. Users can optionally configure these via Advanced Settings to enable additional features. +> [!NOTE] +> AGPL-licensed libraries are not bundled in BentoPDF's source code. They are loaded at runtime from CDN (pre-configured) and can be overridden via environment variables or Advanced Settings. Your work inspires and empowers developers everywhere. Thank you for making open-source amazing! diff --git a/cloudflare/WASM-PROXY.md b/cloudflare/WASM-PROXY.md index 1a762c4..c7ee907 100644 --- a/cloudflare/WASM-PROXY.md +++ b/cloudflare/WASM-PROXY.md @@ -36,6 +36,18 @@ npx wrangler secret put CPDF_SOURCE -c wasm-wrangler.toml ### 3. Configure BentoPDF +**Option A: Environment variables (recommended — zero-config for users)** + +Set these in `.env.production` or pass as Docker build args: + +```bash +VITE_WASM_PYMUPDF_URL=https://bentopdf-wasm-proxy..workers.dev/pymupdf/ +VITE_WASM_GS_URL=https://bentopdf-wasm-proxy..workers.dev/gs/ +VITE_WASM_CPDF_URL=https://bentopdf-wasm-proxy..workers.dev/cpdf/ +``` + +**Option B: Manual per-user configuration** + In BentoPDF's Advanced Settings (wasm-settings.html), enter: | Module | URL | diff --git a/docs/licensing.md b/docs/licensing.md index b972035..3f9bb1d 100644 --- a/docs/licensing.md +++ b/docs/licensing.md @@ -30,27 +30,23 @@ For complete licensing information, delivery details, AGPL component notices, an ## Important Notice on Third-Party Components -::: warning AGPL Components - Not Bundled -BentoPDF **does not bundle** AGPL-licensed processing libraries. The following components are loaded separately by users who configure them via **Advanced Settings**: +::: info AGPL Components — Pre-configured via CDN +BentoPDF **does not bundle** AGPL-licensed processing libraries in its source code. These components are loaded at runtime from CDN URLs that are **pre-configured by default** — all features work out of the box with zero setup. -| Component | License | Status | -| --------------- | -------- | ----------------------------- | -| **PyMuPDF** | AGPL-3.0 | Not bundled - user configured | -| **Ghostscript** | AGPL-3.0 | Not bundled - user configured | -| **CoherentPDF** | AGPL-3.0 | Not bundled - user configured | +| Component | License | Status | +| --------------- | -------- | ---------------------- | +| **PyMuPDF** | AGPL-3.0 | Pre-configured via CDN | +| **Ghostscript** | AGPL-3.0 | Pre-configured via CDN | +| **CoherentPDF** | AGPL-3.0 | Pre-configured via CDN | -**Why are AGPL binaries not included?** +WASM module URLs are configured via environment variables at build time (`.env.production`). The defaults point to jsDelivr CDN. For custom deployments (air-gapped, self-hosted), you can override via environment variables, Docker build args, or per-user via **Advanced Settings** in the UI. -To maintain clear legal separation, BentoPDF does not distribute AGPL-licensed binaries. Users who need features powered by these libraries can: - -1. Configure their own WASM sources in Advanced Settings -2. Host their own WASM proxy to serve these files -3. Use any compatible CDN that hosts these packages +See [Self-Hosting > WASM Configuration](/self-hosting/#wasm-configuration-agpl-components) for details. This approach ensures: - BentoPDF's core code remains under its dual-license (AGPL-3.0 / Commercial) -- Users make an informed choice when enabling AGPL features +- WASM binaries are loaded at runtime, not included in the source - Clear compliance boundaries for commercial users ::: diff --git a/docs/self-hosting/docker.md b/docs/self-hosting/docker.md index 8630da2..f9b41ce 100644 --- a/docs/self-hosting/docker.md +++ b/docs/self-hosting/docker.md @@ -88,10 +88,15 @@ docker run -d -p 3000:8080 bentopdf:custom ## Environment Variables -| Variable | Description | Default | -| ------------- | ------------------------------- | ------- | -| `SIMPLE_MODE` | Build without LibreOffice tools | `false` | -| `BASE_URL` | Deploy to subdirectory | `/` | +| Variable | Description | Default | +| ----------------------- | ------------------------------- | -------------------------------------------------------------- | +| `SIMPLE_MODE` | Build without LibreOffice tools | `false` | +| `BASE_URL` | Deploy to subdirectory | `/` | +| `VITE_WASM_PYMUPDF_URL` | PyMuPDF WASM module URL | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.14/` | +| `VITE_WASM_GS_URL` | Ghostscript WASM module URL | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/` | +| `VITE_WASM_CPDF_URL` | CoherentPDF WASM module URL | `https://cdn.jsdelivr.net/npm/coherentpdf/dist/` | + +WASM module URLs are pre-configured with CDN defaults — all advanced features work out of the box. Override these for air-gapped or self-hosted deployments. Example: @@ -102,6 +107,46 @@ docker run -d \ ghcr.io/alam00000/bentopdf:latest ``` +### Custom WASM URLs (Air-Gapped / Self-Hosted) + +> [!IMPORTANT] +> WASM URLs are baked into the JavaScript at **build time**. The WASM files are downloaded by the **user's browser** at runtime — Docker does not download them during the build. For air-gapped networks, you must host the WASM files on an internal server that browsers can reach. + +**Full air-gapped workflow:** + +```bash +# 1. On a machine WITH internet — download WASM packages +npm pack @bentopdf/pymupdf-wasm@0.11.14 +npm pack @bentopdf/gs-wasm +npm pack coherentpdf + +# 2. Build the image with your internal server URLs +docker build \ + --build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \ + --build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \ + --build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \ + -t bentopdf . + +# 3. Export the image +docker save bentopdf -o bentopdf.tar + +# 4. Transfer bentopdf.tar + the .tgz WASM packages into the air-gapped network + +# 5. Inside the air-gapped network — load and run +docker load -i bentopdf.tar + +# Extract WASM packages to your internal web server +mkdir -p /var/www/wasm/pymupdf /var/www/wasm/gs /var/www/wasm/cpdf +tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C /var/www/wasm/pymupdf --strip-components=1 +tar xzf bentopdf-gs-wasm-*.tgz -C /var/www/wasm/gs --strip-components=1 +tar xzf coherentpdf-*.tgz -C /var/www/wasm/cpdf --strip-components=1 + +# Run BentoPDF +docker run -d -p 3000:8080 --restart unless-stopped bentopdf +``` + +Set a variable to empty string to disable that module (users must configure manually via Advanced Settings). + ## With Traefik (Reverse Proxy) ```yaml diff --git a/docs/self-hosting/index.md b/docs/self-hosting/index.md index 10dedf4..260ebd1 100644 --- a/docs/self-hosting/index.md +++ b/docs/self-hosting/index.md @@ -118,12 +118,13 @@ Choose your platform: - [Kubernetes](/self-hosting/kubernetes) - [CORS Proxy](/self-hosting/cors-proxy) - Required for digital signatures -## Configuring AGPL WASM Components +## WASM Configuration (AGPL Components) -BentoPDF **does not bundle** AGPL-licensed processing libraries. Some advanced features require you to configure WASM modules separately. +BentoPDF **does not bundle** AGPL-licensed processing libraries in its source code, but **pre-configures CDN URLs** so all features work out of the box — no manual setup needed. -::: warning AGPL Components Not Included -The following WASM modules are **not bundled** with BentoPDF and must be configured by users who want to use features powered by these libraries: +::: tip Zero-Config by Default +As of v2.0.0, WASM modules are pre-configured to load from jsDelivr CDN via environment variables. All advanced features work immediately without any user configuration. +::: | Component | License | Features | | --------------- | -------- | ---------------------------------------------------------------- | @@ -131,17 +132,103 @@ The following WASM modules are **not bundled** with BentoPDF and must be configu | **Ghostscript** | AGPL-3.0 | PDF/A conversion, compression, deskewing, rasterization | | **CoherentPDF** | AGPL-3.0 | Table of contents, attachments, PDF merge with bookmarks | +### Default Environment Variables + +These are set in `.env.production` and baked into the build: + +```bash +VITE_WASM_PYMUPDF_URL=https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.14/ +VITE_WASM_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/ +VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/coherentpdf/dist/ +``` + +### Overriding WASM URLs + +You can override the defaults at build time for custom deployments: + +```bash +# Via Docker build args +docker build \ + --build-arg VITE_WASM_PYMUPDF_URL=https://your-server.com/pymupdf/ \ + --build-arg VITE_WASM_GS_URL=https://your-server.com/gs/ \ + --build-arg VITE_WASM_CPDF_URL=https://your-server.com/cpdf/ \ + -t bentopdf . + +# Or via .env.production before building from source +VITE_WASM_PYMUPDF_URL=https://your-server.com/pymupdf/ npm run build +``` + +To disable a module entirely (require manual user config via Advanced Settings), set its variable to an empty string. + +Users can also override these defaults at any time via **Advanced Settings** in the UI — user overrides stored in the browser take priority over environment defaults. + +### Air-Gapped / Offline Deployment + +For networks with no internet access (government, healthcare, financial, etc.). The WASM URLs are baked into the JavaScript at **build time** — the actual WASM files are downloaded by the **user's browser** at runtime. So you need to prepare everything on a machine with internet, then transfer it into the isolated network. + +**Step 1: Download the WASM packages** (on a machine with internet) + +```bash +npm pack @bentopdf/pymupdf-wasm@0.11.14 +npm pack @bentopdf/gs-wasm +npm pack coherentpdf +``` + +**Step 2: Build the Docker image with internal URLs** + +```bash +git clone https://github.com/alam00000/bentopdf.git +cd bentopdf + +docker build \ + --build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \ + --build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \ + --build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \ + -t bentopdf . +``` + +**Step 3: Export the Docker image** + +```bash +docker save bentopdf -o bentopdf.tar +``` + +**Step 4: Transfer into the air-gapped network** + +Copy via USB, internal artifact repo, or approved transfer method: + +- `bentopdf.tar` — the Docker image +- The three `.tgz` WASM packages from Step 1 + +**Step 5: Set up inside the air-gapped network** + +```bash +# Load the Docker image +docker load -i bentopdf.tar + +# Extract WASM packages to your internal web server's document root +mkdir -p /var/www/wasm/pymupdf /var/www/wasm/gs /var/www/wasm/cpdf +tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C /var/www/wasm/pymupdf --strip-components=1 +tar xzf bentopdf-gs-wasm-*.tgz -C /var/www/wasm/gs --strip-components=1 +tar xzf coherentpdf-*.tgz -C /var/www/wasm/cpdf --strip-components=1 + +# Run BentoPDF +docker run -d -p 3000:8080 --restart unless-stopped bentopdf +``` + +Make sure the WASM files are accessible at the URLs you configured in Step 2. Users open their browser and everything works — no internet required. + +::: info Building from source instead of Docker? +Set the variables in `.env.production` before running `npm run build`: + +```bash +VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ +VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ +VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ +``` + ::: -### How to Configure WASM Sources - -1. Navigate to **Advanced Settings** in the BentoPDF interface -2. Enter the URLs for the WASM modules you want to use -3. You can use: - - Your own hosted WASM files - - A [WASM proxy](/self-hosting/cors-proxy) you deploy (handles CORS) - - Any compatible CDN hosting these packages - ### Hosting Your Own WASM Proxy If you need to serve AGPL WASM files with proper CORS headers, you can deploy a simple proxy. See the [Cloudflare WASM Proxy guide](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md) for an example implementation. @@ -150,8 +237,8 @@ If you need to serve AGPL WASM files with proper CORS headers, you can deploy a This separation ensures: - Clear legal compliance for commercial users -- Users make informed choices when enabling AGPL features - BentoPDF's core remains under its dual-license (AGPL-3.0 / Commercial) +- WASM files are loaded at runtime, not bundled in the source ::: ## System Requirements diff --git a/src/js/logic/wasm-settings-page.ts b/src/js/logic/wasm-settings-page.ts index da3d9ba..05ae622 100644 --- a/src/js/logic/wasm-settings-page.ts +++ b/src/js/logic/wasm-settings-page.ts @@ -78,16 +78,34 @@ function initializePage() { if (config.pymupdf) { pymupdfUrl.value = config.pymupdf; + if ( + !WasmProvider.isUserConfigured('pymupdf') && + WasmProvider.hasEnvDefault('pymupdf') + ) { + pymupdfUrl.placeholder = config.pymupdf; + } updateStatus('pymupdf', true); } if (config.ghostscript) { ghostscriptUrl.value = config.ghostscript; + if ( + !WasmProvider.isUserConfigured('ghostscript') && + WasmProvider.hasEnvDefault('ghostscript') + ) { + ghostscriptUrl.placeholder = config.ghostscript; + } updateStatus('ghostscript', true); } if (config.cpdf) { cpdfUrl.value = config.cpdf; + if ( + !WasmProvider.isUserConfigured('cpdf') && + WasmProvider.hasEnvDefault('cpdf') + ) { + cpdfUrl.placeholder = config.cpdf; + } updateStatus('cpdf', true); } } @@ -110,8 +128,12 @@ function initializePage() { statusEl.textContent = 'Testing...'; statusEl.className = 'text-xs px-2 py-1 rounded-full bg-yellow-600/30 text-yellow-300'; - } else if (configured) { - statusEl.textContent = 'Configured'; + } else if (configured && WasmProvider.isUserConfigured(packageName)) { + statusEl.textContent = 'Custom Override'; + statusEl.className = + 'text-xs px-2 py-1 rounded-full bg-blue-600/30 text-blue-300'; + } else if (configured || WasmProvider.hasEnvDefault(packageName)) { + statusEl.textContent = 'Pre-configured'; statusEl.className = 'text-xs px-2 py-1 rounded-full bg-green-600/30 text-green-300'; } else { @@ -202,17 +224,25 @@ function initializePage() { clearPyMuPDFCache(); clearGhostscriptCache(); - pymupdfUrl.value = ''; - ghostscriptUrl.value = ''; - cpdfUrl.value = ''; + const defaults = WasmProvider.getAllProviders(); + pymupdfUrl.value = defaults.pymupdf || ''; + ghostscriptUrl.value = defaults.ghostscript || ''; + cpdfUrl.value = defaults.cpdf || ''; - updateStatus('pymupdf', false); - updateStatus('ghostscript', false); - updateStatus('cpdf', false); + updateStatus('pymupdf', WasmProvider.isConfigured('pymupdf')); + updateStatus('ghostscript', WasmProvider.isConfigured('ghostscript')); + updateStatus('cpdf', WasmProvider.isConfigured('cpdf')); + + const hasDefaults = + WasmProvider.hasEnvDefault('pymupdf') || + WasmProvider.hasEnvDefault('ghostscript') || + WasmProvider.hasEnvDefault('cpdf'); showAlert( - 'Cleared', - 'All configurations and cached modules have been cleared.', + 'Reset', + hasDefaults + ? 'Custom overrides cleared. Pre-configured defaults are active.' + : 'All configurations and cached modules have been cleared.', 'success' ); }); diff --git a/src/js/utils/wasm-provider.ts b/src/js/utils/wasm-provider.ts index c7722ef..8f7cebb 100644 --- a/src/js/utils/wasm-provider.ts +++ b/src/js/utils/wasm-provider.ts @@ -8,6 +8,12 @@ interface WasmProviderConfig { const STORAGE_KEY = 'bentopdf:wasm-providers'; +const ENV_DEFAULTS: Record = { + pymupdf: import.meta.env.VITE_WASM_PYMUPDF_URL || undefined, + ghostscript: import.meta.env.VITE_WASM_GS_URL || undefined, + cpdf: import.meta.env.VITE_WASM_CPDF_URL || undefined, +}; + class WasmProviderManager { private config: WasmProviderConfig; private validationCache: Map = new Map(); @@ -31,6 +37,10 @@ class WasmProviderManager { return {}; } + private getEnvDefault(packageName: WasmPackage): string | undefined { + return ENV_DEFAULTS[packageName]; + } + private saveConfig(): void { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(this.config)); @@ -40,7 +50,7 @@ class WasmProviderManager { } getUrl(packageName: WasmPackage): string | undefined { - return this.config[packageName]; + return this.config[packageName] || this.getEnvDefault(packageName); } setUrl(packageName: WasmPackage, url: string): void { @@ -57,11 +67,22 @@ class WasmProviderManager { } isConfigured(packageName: WasmPackage): boolean { + return !!(this.config[packageName] || this.getEnvDefault(packageName)); + } + + isUserConfigured(packageName: WasmPackage): boolean { return !!this.config[packageName]; } + hasEnvDefault(packageName: WasmPackage): boolean { + return !!this.getEnvDefault(packageName); + } + hasAnyProvider(): boolean { - return Object.keys(this.config).length > 0; + return ( + Object.keys(this.config).length > 0 || + Object.values(ENV_DEFAULTS).some(Boolean) + ); } async validateUrl( @@ -172,13 +193,25 @@ class WasmProviderManager { } getAllProviders(): WasmProviderConfig { - return { ...this.config }; + return { + pymupdf: this.config.pymupdf || ENV_DEFAULTS.pymupdf, + ghostscript: this.config.ghostscript || ENV_DEFAULTS.ghostscript, + cpdf: this.config.cpdf || ENV_DEFAULTS.cpdf, + }; } clearAll(): void { this.config = {}; this.validationCache.clear(); - this.saveConfig(); + try { + localStorage.removeItem(STORAGE_KEY); + } catch (e) { + console.error('[WasmProvider] Failed to clear localStorage:', e); + } + } + + resetToDefaults(): void { + this.clearAll(); } getPackageDisplayName(packageName: WasmPackage): string { diff --git a/src/pages/wasm-settings.html b/src/pages/wasm-settings.html index fe5dfbb..6dfebfa 100644 --- a/src/pages/wasm-settings.html +++ b/src/pages/wasm-settings.html @@ -89,19 +89,19 @@
-

- Why is this needed? Some advanced features - require external processing modules that are licensed under - AGPL-3.0. By providing your own module URLs, you can enable - these features. +

+ Pre-configured and ready to use. Advanced + processing modules are loaded automatically from CDN. You can + override the URLs below if you need to use a custom source + (e.g., for air-gapped or self-hosted deployments).

@@ -260,7 +260,7 @@ id="clear-btn" class="px-6 py-2.5 bg-red-600/20 hover:bg-red-600/30 text-red-400 rounded-lg font-medium transition-colors border border-red-600/50" > - Clear All + Reset to Defaults