Demo frontend¶
Cumments is a backend-only project: it exposes an HTTP API and SSE stream, and speaks Matrix through its AppService. The repository does not ship a production frontend. Website and SSG-template developers are expected to write their own comment section for their own pages, using the HTTP API (challenge, PoW, signing, comments, SSE).
misc/demo/ is a standalone demo (no build step: index.html +
demo.css + demo.js) that exercises the full API: it connects to a real
backend and supports posting, editing and deleting your own comments,
pagination, nested replies, SSE live updates, and a "My comments" management
view. Use it as a reference implementation, not as a reusable component.
The demo posts directly to the API, so it works with origin-mode sites
(including unverified sites under the optional policy). In strict
(secret) mode the frontend must call its own backend instead — see
site-verification.md for the edge-function pattern.
The target site must be registered first (every policy rejects unknown
site_ids): run cumments sites register --site-id <id> or
POST /api/v1/sites, then enter the same id in the demo settings.
Running the demo¶
The demo has no build step. Open misc/demo/index.html in a browser and set
the API URL in the settings drawer (default http://localhost:7931). It loads
Tailwind, Markdown, DOMPurify and BIP39 from CDNs; BIP39 is fetched via
dynamic import() because static ES modules are blocked under file://. If
the CDN is unreachable the demo falls back to a random identity (see below).
If you open the file directly (file://), the browser sends Origin: null.
Cumments accepts that only under the dev-only
security.site_verification = "disabled" policy; with optional or
required, serve the demo over HTTP(S) instead.
The demo also needs a secure context for WebCrypto Ed25519: file:// and
http://localhost work, but a LAN page served over plain http://192.168.x.x
or another non-localhost address will fail at identity creation. Serve the
demo over HTTPS unless you are testing on localhost.
The demo absolutizes API media URLs against the configured API base before
rendering them, so avatars, stickers and image/video/audio attachments also
load when the page is opened directly via file:// (the API must still be
reachable from the browser).
The demo has a built-in language switcher (中文 / EN) in the top bar; the
choice is remembered in localStorage (cumments_demo_lang).
Identity¶
Generate an Ed25519 keypair with WebCrypto and keep the private key in the
browser. The public key is the identity: send it as author_public_key,
and sign the canonical request message with the private key. Edit/delete are
authorized by comparing the presented public key to the one stored with the
comment and verifying the signature.
Identity recovery is mnemonic-first: a fresh identity is derived from a BIP39
12-word English mnemonic via SLIP-0010 at the fixed path m/44'/1328'/0'. The
mnemonic is not persisted across sessions — it lives only in the current tab's
session storage, is shown once at creation, and can be viewed again from the
settings drawer within the same session — so you must write it down. The
derived private key is cached in localStorage; clearing browser data removes
that cache, but the mnemonic is the offline backup — entering it again in the
settings drawer re-derives the exact same identity and writes it back. The
mnemonic itself is deliberately kept out of long-lived storage, so it stays
separate from the local cache (paper, a password manager, or another device).
As an advanced option, the settings drawer can export the identity as a JSON
file ({version, publicKey, privateKey}) and import it back; imports are
rejected when the private key does not match the stated public key. If the
BIP39 CDN is unreachable, the demo falls back to a random Ed25519 identity and
reminds you that mnemonic recovery is unavailable.
Avatars¶
Comment authors with an avatar render it as an image (the API's signed 96×96
crop proxy URL) and fall back to the deterministic initial block when the
image cannot load. The room header prefers avatar_thumbnail_url over the
full-size avatar.
The settings drawer's identity section can upload and remove the visitor
avatar for the current site. Uploads are restricted to images, downscaled
client-side to a square 512×512 PNG, and signed with
["UPLOAD_AVATAR", site_id, mime, sha256_hex(body), challenge]; removal uses
["DELETE_AVATAR", site_id, challenge] (see
Media API). Avatars are per-site because the
virtual user is derived from site_id + public_key.
The demo keeps the last known avatar URL in localStorage per site and falls
back to the newest own comment's avatar when the cache is empty (e.g. after
restoring an identity on another device). Raw mxc:// URIs are never used as
image sources; only the API's signed proxy URLs are rendered.
Author display names and avatars are rendered from the current room-member profile, so renaming or changing an avatar updates previously posted comments too; the local cache only fills the gap before the author has any projected comment after restoring an identity.
Proof of work¶
- Call
GET /api/v1/challenge. - Find a
noncesuch thatSHA256(prefix + nonce)starts withdifficultyleading zero hex digits. - Submit
challenge_response = prefix + "|" + nonce.
The canonical signing messages are documented in the API reference.