Skip to content

Do not expose blob: URLs to users; keep asset:// as the only user-visible and persisted asset reference #1598 #33

@erseco

Description

@erseco

Summary

Users should never see or copy blob: URLs inside eXeLearning editors or HTML source views.

blob: URLs are browser-temporary runtime URLs. They are not stable project references, they are not portable, and users can mistakenly copy them into their content thinking they are the real asset path.

The editor should expose only asset://... references to users and internally translate them to temporary blob: URLs only when needed for rendering or preview.

Problem

At the moment, users can end up seeing blob: URLs such as:

blob:https://static.exelearning.dev/62e7c455-f41c-45a5-a42a-9a8864d22cac

This is misleading because:

  • blob: URLs are temporary and browser/session-specific.
  • They are not suitable for persistence in project content.
  • Some users copy them manually into HTML.
  • This can produce broken references, confusion, and hard-to-understand editing errors.

From a user perspective, the only meaningful reference should be something stable and project-aware, for example:

asset://8f3e9c2a-1b4d-4f7e-9a3c-2d1e5f7a9b3c.jpg

or, in a friendlier future form:

asset://images/photo.jpg

with the application internally resolving that to the current asset UUID and to a runtime blob: URL when rendering.

Current architecture context

The current codebase already seems aligned with this direction:

  • The architecture documentation describes assets as using asset://... URLs as the content-addressable reference format.
  • AssetManager also documents that assets are referenced with asset:// URLs in HTML.
  • The same file clearly distinguishes those stored references from display-time blob URLs created with URL.createObjectURL(...).

That means this issue is less about changing the storage model and more about enforcing a stricter boundary between:

  • canonical persisted referencesasset://...
  • temporary runtime rendering URLsblob:...

Proposal

Phase 1: low-risk fix

Ensure that users never see blob: URLs in editable content.

Concretely:

  1. Do not write blob: URLs into the persisted HTML/Yjs content

    • Before save / serialization, rewrite any known local blob reference back to its corresponding asset://... URL.
    • If a blob URL cannot be mapped to a known asset, show a validation warning and prevent saving that broken reference.
  2. Do not show blob: URLs in source/code editing dialogs

    • If the user opens HTML/source mode, the content shown should contain only asset://... references.
  3. Do not insert blob: URLs when using media/file pickers

    • Insert asset://... directly into the editor model.
    • Convert to blob: only in the rendering layer.
  4. Add a defensive cleanup step on paste/import/save

    • Detect blob: references in HTML.
    • If they match a locally known asset, replace them automatically with asset://....
    • If not, notify the user that temporary browser URLs are not valid project references.

Phase 2: optional usability improvement

Introduce a friendlier alias format for display, while keeping the UUID-based model internally:

asset://folder/image.jpg

Possible behavior:

  • UI/source editor displays asset://folder/image.jpg
  • internally the model still resolves that to the canonical asset UUID
  • export/runtime resolution continues using the current asset metadata

This would be more understandable for non-technical users than a raw UUID.


Suggested implementation approach

A simple implementation path could be:

A. Enforce canonicalization in one place

Create a single utility responsible for normalizing asset references in HTML:

  • input: raw HTML from editor/source/import/paste
  • output: normalized HTML with only asset://... references

This utility could:

  • replace known blob: URLs using the existing reverse blob cache / asset metadata
  • preserve valid asset://... references
  • ignore http:, https:, data:, anchors, etc.
  • optionally report unknown blob: references

This avoids scattering the logic across multiple editors.

B. Apply normalization at the boundary points

Use that normalization in these moments:

  • after inserting media from the file manager
  • before writing HTML into Yjs / project state
  • before opening source editor views
  • during import/paste sanitization

C. Keep runtime translation separate

Rendering layers can still resolve asset://... to blob: internally for preview, iframe rendering, or local display.

That preserves the current runtime behavior while making the persisted/user-facing format predictable.

Why this is a good fit for the current design

This proposal works well with the current architecture because the project already has:

  • a dedicated asset metadata layer
  • canonical asset:// references documented as the intended format
  • a separate blob resolution mechanism for display/runtime

So the change is mainly about enforcing the intended abstraction consistently in the editor UI.

Advantages

  • Prevents users from copying temporary browser-only URLs
  • Reduces confusion in HTML/source editing
  • Makes saved content deterministic and portable
  • Keeps project content independent from a specific browser session
  • Makes debugging easier because persisted references are stable
  • Fits the existing asset:// architecture instead of introducing a new storage model

Trade-offs / possible drawbacks

  • Existing code paths may already depend on blob: being inserted in some editors, so a few UI integrations may need adjustment.
  • A strict save-time validation may initially surface previously hidden bad content.
  • A friendly path-based syntax such as asset://folder/image.jpg is more readable, but it introduces ambiguity around rename/move operations unless it is clearly treated as a UI alias rather than the canonical stored identifier.
  • Keeping UUID-based asset://uuid.ext as the canonical stored form is safer for deduplication and rename/move independence.

Recommended scope for this issue

I would recommend implementing this issue in two steps:

  1. Fix now

    • never expose blob: to users
    • always persist asset://...
    • normalize blob: back to asset://... before save
  2. Evaluate later

    • whether a friendlier asset://path/file.ext alias should be introduced for UI/source editing

Acceptance criteria

  • Users do not see blob: URLs in HTML/source editing workflows.

  • Saving content never persists blob: references.

  • Media insertion writes asset://... references into the editor model.

  • Any detected blob: URL in editable HTML is either:

    • automatically converted to the matching asset://..., or
    • rejected with a clear validation message if it cannot be resolved.
  • Preview/runtime rendering can still use blob: internally without exposing it to the user.

Possible places to review in the codebase

The relevant parts appear to be around:

  • public/app/yjs/AssetManager.js
  • editor/workarea integration points that insert or serialize HTML
  • preview/runtime asset resolution code

A good first step would be to identify every code path where URL.createObjectURL(...) or resolved blob URLs are injected into editable DOM/source content, and replace that with canonical asset://... insertion plus runtime-only resolution.

Final note

The main goal is not to remove blob: from the runtime, but to make it an internal implementation detail.

For users, the project should behave as if asset references are always stable, portable and understandable.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions