Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Interactive static assets imported as SVG, MP3 or MP4 never load because the service worker rejects their data URLs

Open
#1,796 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
84/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
nodejs, typescript
Domain
backend, testing

Research direction

Start with packages/runtime-internal/src/interactive/worker.js:22-51 and the existing cases in packages/runtime-internal/src/interactive/__tests__/worker.test.ts; compare the accepted URL forms with the examples from packages/instrument-bundler/src/utils.ts:24. Add coverage for the SVG, MP3, and MP4 forms, checking each response’s Content-Type and decoded body. Done when those tests pass and the service worker serves all three asset types.

Written by the indexing model from the issue text.

Description

Area: Instruments Bug Difficulty: Low Good First Issue Priority: Medium

The interactive iframe's service worker cannot serve SVG, MP3 or MP4 static assets, though the bundler supports importing all three. instrument-bundler loads .svg, .mp3 and .mp4 with esbuild's dataurl loader, which (esbuild 0.28) produces data:image/svg+xml,<svg …> (percent-encoded text, not base64), data:audio/mpeg;base64,… and data:video/mp4;base64,…. worker.js only accepts data:image/*;base64,… and data:text/*,…, so it throws for all three. The throw happens before respondWith is called, so the request falls through to the network, which has no such file. An author who writes import tone from './tone.mp3' and staticAssets: { '/tone.mp3': tone }, as the instrument guidelines describe, gets audio that never plays and SVG stimuli that never appear, with nothing reported to the host.

Where

packages/runtime-internal/src/interactive/worker.js:22-51:

function dataUrlToResponse(dataUrl) {
  // Try base64 image format first
  const base64Match = /^data:(image\/[a-zA-Z+]+);base64,([A-Za-z0-9+/=]+)$/.exec(dataUrl);
  // ...
  // Try plain text format
  const textMatch = /^data:(text\/[a-zA-Z+]+),(.*)$/.exec(dataUrl);
  // ...
  throw new Error('Invalid or unsupported data URL: must be a base64-encoded image or plain text');
}

and the loader that produces those URLs, packages/instrument-bundler/src/utils.ts:24:

    .with(P.union('.jpeg', '.jpg', '.png', '.svg', '.webp', '.mp3', '.mp4'), () => 'dataurl' as const)

Reproduce

  1. In the playground, copy the "Interactive Instrument with Static Assets" example and add a tone.mp3 and an icon.svg.
  2. Import both (import tone from './tone.mp3', import icon from './icon.svg'), add them to staticAssets as '/tone.mp3' and '/icon.svg', and reference them from fragment.html (<audio src="tone.mp3" controls>, <img src="icon.svg">).
  3. Open the instrument in apps/web.

Actual: the service worker logs Invalid or unsupported data URL: must be a base64-encoded image or plain text for each request, both requests go to the server and fail, the audio element cannot play and the SVG image is broken. The PNG in the same instrument loads.
Expected: every data URL the bundler can produce for an import is served with its own MIME type.

Tests

packages/runtime-internal/src/interactive/__tests__/worker.test.ts: it('should serve every data URL the bundler emits, so svg, mp3 and mp4 static assets load'). Use the exact forms esbuild produces: data:image/svg+xml,<svg …>, data:audio/mpeg;base64,… and data:video/mp4;base64,…. Assert the Content-Type and the decoded body of each.

e2e, in testing/src/specs/instrument-completion.spec.ts (or a playground spec): render an interactive instrument whose staticAssets include an SVG and assert the image's naturalWidth is greater than 0.

Suggested fix

Parse any data:<mime>[;base64],<payload> generically. Take the MIME type from the URL, base64-decode when ;base64 is present, and otherwise decodeURIComponent the payload (esbuild's text form is percent-escaped). Then respond with those bytes. Keep throwing only for strings that are not data URLs at all.

Dominant language
TypeScript
Stars
119
Forks
19
Avg merge
1d 4h
Merged PRs (30d)
44

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from DouglasNeuroInformatics/OpenDataCapture

All issues in DouglasNeuroInformatics/OpenDataCapture

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.