fix: serve engine modules with JavaScript MIME

This commit is contained in:
2026-07-27 13:08:06 +02:00
parent a50944e7cb
commit 43670a5824
15 changed files with 68 additions and 48 deletions

View File

@@ -4,5 +4,5 @@ TRAEFIK_NETWORK=internal
TRAEFIK_CERT_RESOLVER=netcup TRAEFIK_CERT_RESOLVER=netcup
# Keep these two values paired when deploying a newer published toolbox release. # Keep these two values paired when deploying a newer published toolbox release.
TOOLBOX_RELEASE_VERSION=0.9.0 TOOLBOX_RELEASE_VERSION=0.9.1
TOOLBOX_RELEASE_SHA256=fa3c5b8a4148dbb360acc04fd505a6fa64e06c5c842cf1334af5c7d232208f3c TOOLBOX_RELEASE_SHA256=e9dc6a949f1348694099baf62a7b86aa6fea1805bca794de7dfab5dd2040e062

View File

@@ -2,8 +2,8 @@ ARG NGINX_IMAGE=nginxinc/nginx-unprivileged:1.31.3-alpine@sha256:18d67281256ded3
FROM ${NGINX_IMAGE} AS release FROM ${NGINX_IMAGE} AS release
ARG TOOLBOX_RELEASE_VERSION=0.9.0 ARG TOOLBOX_RELEASE_VERSION=0.9.1
ARG TOOLBOX_RELEASE_SHA256=fa3c5b8a4148dbb360acc04fd505a6fa64e06c5c842cf1334af5c7d232208f3c ARG TOOLBOX_RELEASE_SHA256=e9dc6a949f1348694099baf62a7b86aa6fea1805bca794de7dfab5dd2040e062
ARG TOOLBOX_RELEASE_BASE_URL=https://git.add-ideas.de/zemion/toolbox-portal/releases/download ARG TOOLBOX_RELEASE_BASE_URL=https://git.add-ideas.de/zemion/toolbox-portal/releases/download
RUN set -eu; \ RUN set -eu; \

View File

@@ -1,7 +1,7 @@
# add·ideas Toolbox Portal # add·ideas Toolbox Portal
`toolbox-portal` is the static launcher and release assembler for the add·ideas `toolbox-portal` is the static launcher and release assembler for the add·ideas
browser toolbox. Version `0.2.8` reads one same-origin catalogue, shows each app browser toolbox. Version `0.2.9` reads one same-origin catalogue, shows each app
as a compact launch tile, and keeps personal pins, drag-and-drop ordering, as a compact launch tile, and keeps personal pins, drag-and-drop ordering,
visibility, and appearance in the current browser. Pinned tools have their own visibility, and appearance in the current browser. Pinned tools have their own
section; search, category, and clickable tag filters stay close to the tool section; search, category, and clickable tag filters stay close to the tool
@@ -69,7 +69,7 @@ privacy and executable-code warning. Light, dark, and system modes are supported
## Production deployment behind Traefik ## Production deployment behind Traefik
The committed `compose.yaml` is the shortest production path. Its release The committed `compose.yaml` is the shortest production path. Its release
container downloads the immutable Toolbox 0.9.0 ZIP during the image build, container downloads the immutable Toolbox 0.9.1 ZIP during the image build,
verifies SHA-256 before extracting it, and then copies only the verified static verifies SHA-256 before extracting it, and then copies only the verified static
files into the pinned unprivileged nginx image. Node.js and a local portal files into the pinned unprivileged nginx image. Node.js and a local portal
assembly are not required on the deployment host. assembly are not required on the deployment host.
@@ -121,9 +121,9 @@ The app release should also publish a matching `.sha256` sidecar. The manifest
must declare the pinned reverse-DNS id and version. Application and portal must declare the pinned reverse-DNS id and version. Application and portal
versions are independent. versions are independent.
`release/toolbox.lock.json` is the reviewed v0.9.0 lock. Its URLs and checksums `release/toolbox.lock.json` is the reviewed v0.9.1 lock. Its URLs and checksums
pin the published PDF Tools 0.4.3, XSLT Tools 0.4.2, OneNote Tools 0.3.3, pin the published PDF Tools 0.4.3, XSLT Tools 0.4.2, OneNote Tools 0.3.3,
Audio & Video Tools 0.2.0, and Regex Tools 0.4.0 release bytes. Use Audio & Video Tools 0.2.0, and Regex Tools 0.4.1 release bytes. Use
`release/toolbox.lock.example.json` as the template for a future release and `release/toolbox.lock.example.json` as the template for a future release and
verify every downloaded asset before committing updated values. Artifacts may be: verify every downloaded asset before committing updated values. Artifacts may be:
@@ -144,7 +144,7 @@ npm run assemble -- \
--lock release/toolbox.lock.json \ --lock release/toolbox.lock.json \
--portal-dist dist \ --portal-dist dist \
--output build/toolbox \ --output build/toolbox \
--archive build/add-ideas-toolbox-0.9.0.zip --archive build/add-ideas-toolbox-0.9.1.zip
``` ```
Existing output is refused. Pass `--force` only when replacing those exact Existing output is refused. Pass `--force` only when replacing those exact
@@ -166,8 +166,8 @@ build/toolbox/
├── av/ ├── av/
└── regex/ └── regex/
build/add-ideas-toolbox-0.9.0.zip build/add-ideas-toolbox-0.9.1.zip
build/add-ideas-toolbox-0.9.0.zip.sha256 build/add-ideas-toolbox-0.9.1.zip.sha256
``` ```
The assembler verifies SHA-256 before opening an artifact, validates every app The assembler verifies SHA-256 before opening an artifact, validates every app
@@ -188,7 +188,7 @@ directory separately:
```sh ```sh
npm run package:static -- \ npm run package:static -- \
--input build/toolbox \ --input build/toolbox \
--output artifacts/add-ideas-toolbox-0.9.0.zip --output artifacts/add-ideas-toolbox-0.9.1.zip
``` ```
This also emits a `.sha256` sidecar. This also emits a `.sha256` sidecar.
@@ -196,7 +196,8 @@ This also emits a `.sha256` sidecar.
## Local assembled container and publication ## Local assembled container and publication
`Containerfile` serves only the assembled files with unprivileged nginx on port 8080. It adds restrictive browser headers, same-origin isolation, explicit `Containerfile` serves only the assembled files with unprivileged nginx on port 8080. It adds restrictive browser headers, same-origin isolation, explicit
`application/wasm`, immutable caching only for Vite-hashed assets and narrowly `application/javascript` for `.mjs` engine modules and `application/wasm`,
immutable caching only for Vite-hashed assets and narrowly
matched semver-versioned FFmpeg core files, and revalidation for all other matched semver-versioned FFmpeg core files, and revalidation for all other
files. Assemble first, then: files. Assemble first, then:
@@ -206,9 +207,9 @@ normal local XML/XSLT transformations do not require that permission.
```sh ```sh
podman build -f Containerfile \ podman build -f Containerfile \
-t git.add-ideas.de/zemion/toolbox:0.9.0 . -t git.add-ideas.de/zemion/toolbox:0.9.1 .
podman run --rm -p 8080:8080 \ podman run --rm -p 8080:8080 \
git.add-ideas.de/zemion/toolbox:0.9.0 git.add-ideas.de/zemion/toolbox:0.9.1
``` ```
For a direct-port local deployment after assembly, use the separate example: For a direct-port local deployment after assembly, use the separate example:
@@ -225,7 +226,7 @@ docker login git.add-ideas.de
docker buildx build \ docker buildx build \
--platform linux/amd64,linux/arm64 \ --platform linux/amd64,linux/arm64 \
--file Containerfile.release \ --file Containerfile.release \
--tag git.add-ideas.de/zemion/toolbox:0.9.0 \ --tag git.add-ideas.de/zemion/toolbox:0.9.1 \
--tag git.add-ideas.de/zemion/toolbox:0.9 \ --tag git.add-ideas.de/zemion/toolbox:0.9 \
--push . --push .
``` ```
@@ -251,8 +252,8 @@ needs permission to push code, releases, and container packages to
all five apps, switch between them, and confirm the encoded `toolbox` all five apps, switch between them, and confirm the encoded `toolbox`
context. context.
5. Verify the distribution with 5. Verify the distribution with
`(cd build && sha256sum -c add-ideas-toolbox-0.9.0.zip.sha256)`. `(cd build && sha256sum -c add-ideas-toolbox-0.9.1.zip.sha256)`.
6. Commit the reviewed lock, tag the toolbox release (for example `v0.9.0`), and 6. Commit the reviewed lock, tag the toolbox release (for example `v0.9.1`), and
push the branch and tag to Gitea. push the branch and tag to Gitea.
7. In Gitea, open **Releases → New release**, select the tag, and upload the 7. In Gitea, open **Releases → New release**, select the tag, and upload the
static ZIP plus its `.sha256` file. Do not use a mutable “latest” URL in a static ZIP plus its `.sha256` file. Do not use a mutable “latest” URL in a
@@ -268,12 +269,12 @@ must not re-resolve app versions or substitute a newer release.
## Existing tools ## Existing tools
| Tool | State and boundary | Principal workflows | Important concrete scope | Critical considerations and integrations | | Tool | State and boundary | Principal workflows | Important concrete scope | Critical considerations and integrations |
| ------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`pdf-tools`** | Existing; dedicated repository | Merge, split, reorder, rotate and export PDF pages | Thumbnail workspace; multi-document operations; ZIP and PDF output; saved local workspace | Workers and IndexedDB; large-document memory control; future signature inspection through `crypto-tools`; metadata and safe-sharing through `privacy-tools` | | **`pdf-tools`** | Existing; dedicated repository | Merge, split, reorder, rotate and export PDF pages | Thumbnail workspace; multi-document operations; ZIP and PDF output; saved local workspace | Workers and IndexedDB; large-document memory control; future signature inspection through `crypto-tools`; metadata and safe-sharing through `privacy-tools` |
| **`xslt-tools`** | Existing; dedicated repository | Develop, test and run XSLT transformations | XML/XSLT editors; transformation results; local files; saved projects; validation and diagnostics | Lazy SaxonJS loading; relocatable assets; useful handoffs to `data-tools`, `schema-tools` and `diff-tools` | | **`xslt-tools`** | Existing; dedicated repository | Develop, test and run XSLT transformations | XML/XSLT editors; transformation results; local files; saved projects; validation and diagnostics | Lazy SaxonJS loading; relocatable assets; useful handoffs to `data-tools`, `schema-tools` and `diff-tools` |
| **`onenote-tools`** | Existing rich-reader release; dedicated repository | Open, browse, inspect and export OneNote sections and packages locally | Desktop/unfragmented FSSHTTPB `.one`/`.onetoc2`; none/LZX/MSZIP/Quantum and multi-cabinet `.onepkg`; trees, rich text, images, tables, ink, attachments and export | Native TypeScript worker with no Wasm; bounded parsing, rendering and export; fragmented FSSHTTP fails safely; no editing or pixel-perfect fidelity | | **`onenote-tools`** | Existing rich-reader release; dedicated repository | Open, browse, inspect and export OneNote sections and packages locally | Desktop/unfragmented FSSHTTPB `.one`/`.onetoc2`; none/LZX/MSZIP/Quantum and multi-cabinet `.onepkg`; trees, rich text, images, tables, ink, attachments and export | Native TypeScript worker with no Wasm; bounded parsing, rendering and export; fragmented FSSHTTP fails safely; no editing or pixel-perfect fidelity |
| **`av-tools`** | Existing local-first v0.2.0 release; dedicated repository | Convert and lightly edit audio and video locally in the browser | Quick conversion; trim, split and crossfade concatenate; crop, resize, normalize, fades, waveform, metadata, chapters, subtitles, scene thumbnails/contact sheets, presets and export | Reviewed ffmpeg.wasm 0.12.10 ST/MT build with verified Opus and bundled label font; bounded queue, temporary storage and resource policy; isolation enables MT with automatic ST fallback; versioned core assets are immutable | | **`av-tools`** | Existing local-first v0.2.0 release; dedicated repository | Convert and lightly edit audio and video locally in the browser | Quick conversion; trim, split and crossfade concatenate; crop, resize, normalize, fades, waveform, metadata, chapters, subtitles, scene thumbnails/contact sheets, presets and export | Reviewed ffmpeg.wasm 0.12.10 ST/MT build with verified Opus and bundled label font; bounded queue, temporary storage and resource policy; isolation enables MT with automatic ST fallback; versioned core assets are immutable |
| **`regex-tools`** | Current local-first v0.4.0 release; dedicated repository | Develop, explain, test and apply JavaScript, PCRE2, PHP, Perl, Python, Ruby, Java, C++, Go, .NET, Rust and Scala/JVM-compatible regular expressions | Deterministic syntax trees; engine-native matching, captures and bounded replacement; PCRE2 tracing and C17 generation; comparison; corpus apply; tests; risk/growth/benchmark analysis; generated cases; bounded minimization; validated ECMAScript formatting; project import/export and optional local persistence | Open-source regexpp and pinned local WebAssembly runtimes for PCRE2, PHP preg, legacy Perl, CPython, CRuby, TeaVM Java/Scala compatibility, libc++ C++, Go, .NET and Rust in killable workers; explicit source/licence inventories, resource limits and engine-specific offset semantics | | **`regex-tools`** | Current local-first v0.4.1 release; dedicated repository | Develop, explain, test and apply JavaScript, PCRE2, PHP, Perl, Python, Ruby, Java, C++, Go, .NET, Rust and Scala/JVM-compatible regular expressions | Deterministic syntax trees; engine-native matching, captures and bounded replacement; stable double-buffered live results; PCRE2 tracing and C17 generation; comparison; corpus apply; tests; risk/growth/benchmark analysis; generated cases; bounded minimization; validated ECMAScript formatting; project import/export and optional local persistence | Open-source regexpp and pinned local WebAssembly runtimes for PCRE2, PHP preg, legacy Perl, CPython, CRuby, TeaVM Java/Scala compatibility, libc++ C++, Go, .NET and Rust in killable workers; explicit source/licence inventories, resource limits and engine-specific offset semantics |
## Planned tools ## Planned tools

View File

@@ -3,7 +3,7 @@ services:
build: build:
context: . context: .
dockerfile: Containerfile dockerfile: Containerfile
image: git.add-ideas.de/zemion/toolbox:0.9.0 image: git.add-ideas.de/zemion/toolbox:0.9.1
restart: unless-stopped restart: unless-stopped
read_only: true read_only: true
ports: ports:

View File

@@ -6,9 +6,9 @@ services:
context: . context: .
dockerfile: Containerfile.release dockerfile: Containerfile.release
args: args:
TOOLBOX_RELEASE_VERSION: "${TOOLBOX_RELEASE_VERSION:-0.9.0}" TOOLBOX_RELEASE_VERSION: "${TOOLBOX_RELEASE_VERSION:-0.9.1}"
TOOLBOX_RELEASE_SHA256: "${TOOLBOX_RELEASE_SHA256:-fa3c5b8a4148dbb360acc04fd505a6fa64e06c5c842cf1334af5c7d232208f3c}" TOOLBOX_RELEASE_SHA256: "${TOOLBOX_RELEASE_SHA256:-e9dc6a949f1348694099baf62a7b86aa6fea1805bca794de7dfab5dd2040e062}"
image: "git.add-ideas.de/zemion/toolbox:${TOOLBOX_RELEASE_VERSION:-0.9.0}" image: "git.add-ideas.de/zemion/toolbox:${TOOLBOX_RELEASE_VERSION:-0.9.1}"
restart: unless-stopped restart: unless-stopped
read_only: true read_only: true
tmpfs: tmpfs:

View File

@@ -40,6 +40,13 @@ server {
try_files $uri =404; try_files $uri =404;
} }
location ~* \.mjs$ {
types {
application/javascript mjs;
}
try_files $uri =404;
}
location ~* \.(?:css|js|mjs|woff2?|png|jpe?g|gif|webp|svg|ico|webmanifest)$ { location ~* \.(?:css|js|mjs|woff2?|png|jpe?g|gif|webp|svg|ico|webmanifest)$ {
try_files $uri =404; try_files $uri =404;
} }

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@add-ideas/toolbox-portal", "name": "@add-ideas/toolbox-portal",
"version": "0.2.8", "version": "0.2.9",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@add-ideas/toolbox-portal", "name": "@add-ideas/toolbox-portal",
"version": "0.2.8", "version": "0.2.9",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@add-ideas/toolbox-contract": "^0.2.2", "@add-ideas/toolbox-contract": "^0.2.2",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@add-ideas/toolbox-portal", "name": "@add-ideas/toolbox-portal",
"version": "0.2.8", "version": "0.2.9",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"private": true, "private": true,
"type": "module", "type": "module",

View File

@@ -1,8 +1,8 @@
# Corresponding source # Corresponding source
The source code corresponding to add·ideas Toolbox Portal 0.2.8 is available at: The source code corresponding to add·ideas Toolbox Portal 0.2.9 is available at:
https://git.add-ideas.de/zemion/toolbox-portal/src/tag/v0.9.0 https://git.add-ideas.de/zemion/toolbox-portal/src/tag/v0.9.1
Each bundled application contains its own `SOURCE.md`, license, and third-party Each bundled application contains its own `SOURCE.md`, license, and third-party
license materials. The application sources are also linked from the portal. license materials. The application sources are also linked from the portal.

View File

@@ -1,8 +1,8 @@
{ {
"$schema": "./toolbox-release-lock.schema.json", "$schema": "./toolbox-release-lock.schema.json",
"schemaVersion": 1, "schemaVersion": 1,
"releaseVersion": "0.9.0", "releaseVersion": "0.9.1",
"portalVersion": "0.2.8", "portalVersion": "0.2.9",
"catalogue": { "catalogue": {
"id": "de.add-ideas.toolbox", "id": "de.add-ideas.toolbox",
"name": "add·ideas Toolbox", "name": "add·ideas Toolbox",
@@ -43,9 +43,9 @@
}, },
{ {
"id": "de.add-ideas.regex-tools", "id": "de.add-ideas.regex-tools",
"version": "0.4.0", "version": "0.4.1",
"artifact": "https://git.add-ideas.de/zemion/regex-tools/releases/download/v0.4.0/regex-tools-0.4.0.zip", "artifact": "https://git.add-ideas.de/zemion/regex-tools/releases/download/v0.4.1/regex-tools-0.4.1.zip",
"sha256": "68505ed3e302627d889c45c651431d8fb9638056415cad72a28196fd33acd2b4", "sha256": "a298d0c578ee5f3e80c036b1c6be308f0277052e770c000fdda8505e5a305815",
"target": "regex" "target": "regex"
} }
] ]

View File

@@ -1,8 +1,8 @@
{ {
"$schema": "./toolbox-release-lock.schema.json", "$schema": "./toolbox-release-lock.schema.json",
"schemaVersion": 1, "schemaVersion": 1,
"releaseVersion": "0.9.0", "releaseVersion": "0.9.1",
"portalVersion": "0.2.8", "portalVersion": "0.2.9",
"catalogue": { "catalogue": {
"id": "de.add-ideas.toolbox", "id": "de.add-ideas.toolbox",
"name": "add·ideas Toolbox", "name": "add·ideas Toolbox",
@@ -43,9 +43,9 @@
}, },
{ {
"id": "de.add-ideas.regex-tools", "id": "de.add-ideas.regex-tools",
"version": "0.4.0", "version": "0.4.1",
"artifact": "https://git.add-ideas.de/zemion/regex-tools/releases/download/v0.4.0/regex-tools-0.4.0.zip", "artifact": "https://git.add-ideas.de/zemion/regex-tools/releases/download/v0.4.1/regex-tools-0.4.1.zip",
"sha256": "68505ed3e302627d889c45c651431d8fb9638056415cad72a28196fd33acd2b4", "sha256": "a298d0c578ee5f3e80c036b1c6be308f0277052e770c000fdda8505e5a305815",
"target": "regex" "target": "regex"
} }
] ]

View File

@@ -83,7 +83,7 @@ function makeLock(artifact: string, sha256: string) {
return { return {
schemaVersion: 1, schemaVersion: 1,
releaseVersion: '0.1.0', releaseVersion: '0.1.0',
portalVersion: '0.2.8', portalVersion: '0.2.9',
catalogue: { catalogue: {
id: 'de.add-ideas.toolbox', id: 'de.add-ideas.toolbox',
name: 'Test Toolbox', name: 'Test Toolbox',

View File

@@ -196,6 +196,18 @@ describe('production deployment', () => {
); );
}); });
it('serves ES modules as JavaScript before the broad static location', () => {
const moduleLocation =
/location ~\* \\\.mjs\$\s*\{\s*types\s*\{\s*application\/javascript mjs;\s*\}\s*try_files \$uri =404;\s*\}/mu;
const moduleLocationIndex = nginxConfig.search(moduleLocation);
const staticLocationIndex = nginxConfig.search(
/location ~\* \\\.\(\?:css\|js\|mjs\|/u
);
expect(moduleLocationIndex).toBeGreaterThanOrEqual(0);
expect(staticLocationIndex).toBeGreaterThan(moduleLocationIndex);
});
it('allows same-origin WebAssembly workers and local blob media previews', () => { it('allows same-origin WebAssembly workers and local blob media previews', () => {
expect(nginxConfig).toContain("script-src 'self' 'wasm-unsafe-eval'"); expect(nginxConfig).toContain("script-src 'self' 'wasm-unsafe-eval'");
expect(nginxConfig).toContain("worker-src 'self' blob:"); expect(nginxConfig).toContain("worker-src 'self' blob:");

View File

@@ -489,9 +489,9 @@ export default function App() {
</span> </span>
</p> </p>
<span> <span>
Portal v0.2.8 ·{' '} Portal v0.2.9 ·{' '}
<a <a
href="https://git.add-ideas.de/zemion/toolbox-portal/src/tag/v0.9.0" href="https://git.add-ideas.de/zemion/toolbox-portal/src/tag/v0.9.1"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >

View File

@@ -69,7 +69,7 @@ export const regexManifest: ToolboxAppManifest = {
...pdfManifest, ...pdfManifest,
id: 'de.add-ideas.regex-tools', id: 'de.add-ideas.regex-tools',
name: 'Regex Tools', name: 'Regex Tools',
version: '0.4.0', version: '0.4.1',
description: description:
'Develop, explain, test and apply regular expressions locally in the browser.', 'Develop, explain, test and apply regular expressions locally in the browser.',
icon: './regex.svg', icon: './regex.svg',