Skip to content

feat(viewer): server-side asset fallback when the Service Worker can't register#39

Open
erseco wants to merge 6 commits into
mainfrom
feat/viewer-serverside-fallback
Open

feat(viewer): server-side asset fallback when the Service Worker can't register#39
erseco wants to merge 6 commits into
mainfrom
feat/viewer-serverside-fallback

Conversation

@erseco
Copy link
Copy Markdown
Contributor

@erseco erseco commented Jun 6, 2026

Problem

Opening an .elpx in the Nextcloud Playground fails with:

Failed to register a ServiceWorker … A bad HTTP response code (404) was received when fetching the script (/apps/exelearning/sw.js).

The viewer renders a package by registering a scoped Service Worker that streams the package's internal assets from memory. But the Playground is itself a Service Worker emulating the Nextcloud server. When the browser registers a second worker, it fetches that worker's script straight from the network, bypassing the controlling SW (by spec) — so /apps/exelearning/sw.js hits the static host and 404s. (Fetched normally through the page it returns 200; only the registration fetch bypasses the playground SW.)

Fix

When SW registration fails, fall back to the existing server-side AssetController (/apps/exelearning/asset/<fileId>/<entry>) — which already exists "for environments where Service Workers are not viable (e.g. embedded in another origin)" but wasn't wired into the viewer. It extracts and streams each entry from the stored package, so the iframe renders entirely through normal HTTP (which the playground routes to PHP-wasm). No client Service Worker needed.

The SW path stays the default for normal installs; the fallback only kicks in on registration failure and needs the canonical file id (always present in the Files viewer). If there's no usable file id the original error is surfaced as before.

Changes

  • elpx/paths.ts: buildAssetUrl() + ASSET_PREFIX (+ unit tests).
  • elpx/iframe-renderer.ts: extract buildSandboxedIframe() shared by the SW and server paths (identical sandbox flags + external-link rewiring).
  • viewer/ElpxViewer.vue: try SW → on failure attachServerIframe().

Test

npm test (54 pass), npm run lint, npm run typecheck ✅. Verified end-to-end against the Playground preview (see the playground link this PR posts).

…t register

The viewer renders an .elpx by registering a scoped Service Worker that
streams the package's internal assets from memory. That can't work when
Nextcloud is embedded in an origin already owned by a controlling Service
Worker (e.g. the Nextcloud Playground): the browser fetches a worker's
script straight from the network, bypassing the controlling SW, so the
virtual /apps/exelearning/sw.js route 404s and registration throws.

When registration fails, fall back to the existing server-side
AssetController (/apps/exelearning/asset/<fileId>/<entry>), which extracts
and streams each entry from the stored package — no client Service Worker
needed. Requires the canonical file id; otherwise the original error is
surfaced as before.

- paths: buildAssetUrl() + ASSET_PREFIX (+ tests)
- iframe-renderer: extract buildSandboxedIframe() shared by both paths
- ElpxViewer: try SW, catch -> attachServerIframe()
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 6, 2026

Preview this PR in the Nextcloud Playground

Open this PR in the Nextcloud Playground

A fresh Nextcloud boots in your browser with this branch's exelearning app installed and enabled (log in as admin / admin). Two sample .elpx are seeded under exelearning-samples/ in Files — click one to open the viewer.

Bundled eXeLearning editor: v4.0.0 (latest stable release).

erseco added 5 commits June 6, 2026 09:15
…tton

- blueprint: disable firstrunwizard and set core whatsNewEnabled=no so the
  instance lands straight on Files (mirrors the playground default blueprint).
- README + PR comment: use the official playground-preview-button.svg
  (referenced via raw.githubusercontent on the playground repo) at ~50%
  size (width 224), linking to the playground in a new tab.
Resolve the latest stable eXeLearning editor tag once, download exactly
that, and surface it in the PR comment ('Bundled eXeLearning editor:
<version>') instead of the build-artifact line.
…cope

The editor iframe URL came from server-rendered initial state, computed
with an empty webroot. When Nextcloud is served under a sub-path (e.g. the
browser Playground scopes everything under /playground/<scope>/…), that
unscoped URL used as the iframe src escapes the scope and 404s — the host
then waits forever on "Loading editor…".

Build it client-side with generateUrl('/apps/exelearning/editor/iframe'),
which resolves against the live OC.webroot and is correct both in a normal
install and under a scoped path. Same flow in the embedded viewer and the
standalone editor page. Drops the now-unused appWebRoot() helper.
…ks under a scoped path

The editor's embedding config (basePath, parentOrigin, trustedOrigins) was
computed server-side. Under a scoped sub-path (e.g. the browser Playground),
the server's webroot is empty, so basePath was unscoped — the editor's
ResourceFetcher then loaded its theme/content bundles from
/apps/exelearning/js/editor/bundles/*.zip, which escapes the scope and 404s,
and Save failed ("Failed to fetch content/css/base.css").

Derive basePath from document.baseURI (which equals the scoped <base href>
at runtime), and parentOrigin/trustedOrigins from window.location.origin, in
the injected config script. Correct both in a normal install and under a
scoped path.
The bundled eXeLearning editor ships its themes and content CSS as
js/editor/bundles/*.zip (themes/base.zip, content-css.zip, …), which the
editor's ResourceFetcher loads when exporting on Save. .distignore excluded
`*.zip` unanchored, so rsync stripped these nested bundles from the
distribution package — the editor then 404s on Save ("Failed to fetch
content/css/base.css"). This affected both the playground preview and any
real packaged release (make up copies js/ directly, so it only showed in
packaged builds).

Anchor the excludes to the repo root (`/*.zip`, `/*.tar.gz`) so only the
top-level build artifacts are stripped, not the editor's runtime bundles.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant