Skip to content

Parse what the browser receives (TS20) - #30

Merged
Finesssee merged 4 commits into
mainfrom
ts/ts20-browser-parsers
Oct 11, 2026
Merged

Finesssee merged 4 commits into
mainfrom
ts/ts20-browser-parsers

Conversation

@Finesssee

@Finesssee Finesssee commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

TS20: browser code now parses untrusted input once, where it arrives, into typed values. Parsers follow server/parse.ts ({ ok, value } | { ok: false, error }), and the change adds no dependencies.

What changes for viewers and streamers

Nothing changes for valid data. When the server or a stored value sends something malformed:

  • The overlay drops the bad frame and keeps drawing its last good state. A bad player, duel or event is dropped from its list, and the rest still draw.
  • The fighter page, mod page and owner page show "The server sent an answer this page can't read. Reload the page and try again" where they would otherwise have crashed or shown garbled values.
    • An unreadable session answer reads as signed out, and an unreadable access check reads as no access. Neither shows the message.
  • Each kind of drop logs one console.warn ("PixFray dropped ...") per page load.

Parsers and what each does on a failure

Served (src/served/parse.ts, served as /parse.js):

  • parseArenaSnapshot, parseArenaPlayer, parseArenaDuel, parseArenaEvent, parseLook (the /api/live socket in arena-client): a frame that isn't JSON, or has a bad top-level field, is dropped whole. Bad entries are dropped through keepGood.
  • parseLooksMessage (the looks push): dropped whole if it's bad.
  • parseCosmeticSettings (saved chat looks in localStorage): bad entries are dropped. Unreadable JSON reads as empty.
  • parseDemoMessage (postMessage from /start to the demo overlay): ignored if it's bad.
  • parseIRCLine in chat.ts: a line that doesn't parse is skipped.
  • warnOnce and keepGood are the shared helpers.

Pages (src/parse.ts, bundled by Vite):

  • parseAnswer(r, parse, kind) wraps api(). A non-ok answer keeps its error body. An ok answer that fails its parser becomes { ok: false, data: { error: UNREADABLE } }, after one warn.
  • parseSession, parseAccess: used on every page that reads them.
  • Fighter page: parseProfile, parseProfileOrNone, parseProfileSaved, parseBought, parseShop (also the /api/pets fallback), parseStateConfig, parseLeaderboard.
    • A bad saved build becomes null, so the slot numbers stay in place.
  • parseDraft (the sessionStorage draft): bad JSON, or a bad d, reads as no draft, with one warn.
  • Mod page: parseAdmin (/api/admin) and parseAdminSnapshot (its live socket). A bad snapshot is dropped, and the page keeps its last state.
  • Owner page: parseRuns, parseVersions, parseSettings, parseChannels (GET and POST), parseProgress, parseLogs. Bad rows are dropped.

Other notes:

  • Shallow checks: on /api/admin, the history, streamelements, botStatus, customUsage, checkinTest and botCommands panels are checked one level deep. So are the owner page's Cloudflare versions.
  • Arena event dedupe now uses event.id only. The legacy eventId fallback is gone, because the server sends id on every event.
  • /start reads no session. It only posts { demoPaused }, which parseDemoMessage checks on the receiving side.

Left unparsed, and why

These are rarely used or small, and are read where they're used:

  • The catalog, the others list, the picker channel list and the sprite maker.
  • The owner page's diagnostics, code tree, save, pr and restore answers.
  • The gateProof /api/state read.
  • intro/back.ts localStorage, the off-told and reload flags, and KNOWN_KEY.
  • The mod page's /api/assets, /api/pets and /api/sprites reads.
  • Small POST answers that only report ok or error.

Typing fixes and strictness corrections

  • Shop prices can be null, and a pending duel has no startedAt. These were typed wrong before. Runtime behavior is unchanged.

  • During testing, ui.mjs caught three fields where my parser was stricter than the data. These were strictness fixes in the parser, not production bugs:

    • Profile dollars is absent on a fighter who never earned any.
    • Admin overlays may be missing.
    • botStatus may be null.

    All three are now optional, with tests.

  • No production bugs needed a fix.

Docs

  • docs/CONTRACTS.md §3 now says that clients parse every frame and describes the drop behavior.
  • §8 lists the parser modules.
  • docs/DEVELOPMENT.md has the strict-web note and adds the new test to the strict-tests list.

Checks

  • bun run check on the head rebased onto b46170f: 371/371 tests pass.
  • bun run test:all on the rebased head a122193: every step passed. I diffed the TS19 producers (auth, channels, developer) and found no change to any answer shape these parsers read.
  • bun run test:all before the rebase: every step passed (check, port-check, cf build normal and test, module versions, workerd upload, smoke, ui, dev-ui, arena browser, e2e local). During that run I temporarily logged every "PixFray dropped" warning in the browser tests against the real dev Worker. There were 0 drops, and that logging is not in the commits.
  • node scripts/port-check.ts --strict-list: the strict lists cover every TypeScript file. tests/browser-parse.test.ts is on tsconfig.strict-tests.json.
  • bunx cf build: OK.
  • node tests/build-versions.mjs: "PASS: 11 served module references carry ...".
  • node tests/ui.mjs (headed): "PASS: viewer + admin UI at 1280/390, signed-out (real server), signed-in viewer save, mod gate, admin actions, config save/409/revert; no page errors."
  • node tests/arena-browser.mjs (headed): "PASS: authoritative profiles, health snapshots, event deduplication, stale revisions, reconnect, transparent drawing, local-only demo duel, hit effects, fighter-only shake, no stage filter, fx=off and bubbles=0."
    • One run timed out at line 551 (a die===2 wait). The next two runs passed.
  • New unit tests are in tests/browser-parse.test.ts (10 tests), plus a malformed-frame case in tests/arena-client.test.ts.

@coderabbitai

coderabbitai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: nesszer/pixfray/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1783b666-d601-410f-8cd3-b4a5f53ecd81

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Finesssee
Finesssee force-pushed the ts/ts20-browser-parsers branch from fc439a5 to a122193 Compare October 11, 2026 20:13
…us as the pages always did, and document the edge

tests/browser-parse.test.ts covers each parser with a fitting value, a wrong type, a missing field and kept extra fields, plus parseAnswer, the stored draft, dropped list entries and warnOnce. A profile without dollars, an admin answer without overlays and a null botStatus read again, as the pages read them loosely before. docs/CONTRACTS.md §3 and §8 and docs/DEVELOPMENT.md describe the parsers and the new strict test.
@Finesssee
Finesssee force-pushed the ts/ts20-browser-parsers branch from e2cb237 to 9539c78 Compare October 11, 2026 20:30
@Finesssee
Finesssee merged commit 0134389 into main Oct 11, 2026
3 checks passed
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