Skip to content

Parse WebSocket and job data at the boundary - #134

Merged
jR4dh3y merged 3 commits into
hoplite/euhesperides-d9702ac5--mountsfrom
hoplite/euhesperides-d9702ac5--parse-boundary
Oct 1, 2026
Merged

jR4dh3y merged 3 commits into
hoplite/euhesperides-d9702ac5--mountsfrom
hoplite/euhesperides-d9702ac5--parse-boundary

Conversation

@usehoplite

@usehoplite usehoplite Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The browser trusted data it had not checked. WebSocket frames were read with JSON.parse(text) as WSServerMessage, and the payload was then cast again in each branch. An unexpected frame could put a wrong shape into the jobs store. The API client also returned {} as T for any success response that was not JSON, so a proxy error page or the SPA fallback looked like a successful call. On the Go side, JobResponse copied every field of model.Job by hand and re-formatted the dates for no reason.

Change

  • Add parseServerMessage, which turns a raw frame into a discriminated union (job_update | job_complete | error | pong) or null. The store switches on the union with an exhaustiveness check (satisfies never), and logs and ignores frames it does not recognise. No as remains in that path. Progress must be an integer from 0 to 100, and the log line for a rejected frame does not include the frame.
  • Add frontend/src/lib/api/websocket.test.ts (runs with bun run test, using node:test so no new dependency) and a CI step for it.
  • Move JobUpdate to api/jobs.ts with isJobState, so the job state values are listed once.
  • The API client now throws ApiRequestError (INVALID_RESPONSE) when a success response is not JSON. Every handler already returns JSON, so no working call changes. Drive-name calls declared void for a JSON reply and now say unknown.
  • Replace icon: any and its eslint-disable in SettingsSection with typeof ChevronDown.
  • Go: delete JobResponse and toJobResponse and serialise model.Job directly. It already hides Owner and the resolved paths with json:"-". Job times now include fractional seconds (RFC 3339 with nanoseconds), the same format as modTime on files, which the browser already parses. An empty job list is still [].

Left alone: the 40 or so event.target as HTMLInputElement casts in components, and the null as string | null in settings.ts, which goes away when that store is migrated in unit 5. REST payload shapes are still trusted, not validated.

Verification

  • New handler test: an empty list returns {"jobs":[]}. A job response contains no owner or resolved paths, omits a zero startedAt, and has a createdAt that parses as RFC 3339 with nanoseconds.
  • New parser tests cover valid and invalid frames, including progress of -1, 101 and 12.5. They failed on the first version of the parser (range not checked) and pass now. bun run test passes.
  • bun run check, lint and build pass. go vet and the handler tests pass.
  • In a real browser against the stack-tip build, a copy job returned 202 and the page received newline-batched job_update frames (running, progress 0 to 100, then completed). The parser split them correctly and the copied file matched.
  • Not tested: a job_complete or error frame from a real server, and a proxy that returns HTML with status 200 (the new INVALID_RESPONSE path).

Stack: 4 of 6. Targets the branch of the PR below it.


Written by anthropic/claude-sonnet-5-5 in the Hoplite agent harness.

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
box-box Ready Ready Preview Oct 1, 2026 8:03am UTC

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 4f4594f3-240b-4d8b-8ba1-c524252f436f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

Comment thread frontend/src/lib/api/websocket.ts Outdated
Comment on lines +19 to +21
if (typeof jobId !== 'string' || !isJobState(state) || typeof progress !== 'number') {
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Invalid progress passes validation. Job progress is defined as 0–100, but this check accepts any number. If a malformed frame names an existing job, the jobs store writes values such as -1 or 5000 directly. Validate the range before accepting the update.

Suggested change
if (typeof jobId !== 'string' || !isJobState(state) || typeof progress !== 'number') {
return null;
}
if (
typeof jobId !== 'string' ||
!isJobState(state) ||
typeof progress !== 'number' ||
!Number.isInteger(progress) ||
progress < 0 ||
progress > 100
) {
return null;
}

}

/** Returns null for malformed JSON, an unknown type, or a payload of the wrong shape. */
export function parseServerMessage(text: string): WSServerMessage | null {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Parser lacks persistent tests. This parser now decides which WebSocket frames can update job state, but its valid and invalid cases have no committed regression tests. The described 12-frame check was a throwaway script. Persistent tests would catch changes that accept malformed updates or discard valid server messages.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread frontend/src/lib/stores/websocket.ts Outdated
if (message) {
handleServerMessage(message);
} else {
console.warn('Ignoring unrecognised WebSocket message:', text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Rejected frames logged verbatim. Every malformed or unrecognised frame is now printed in full. An unexpected job message could contain user paths or a large payload, making browser diagnostics noisy and retaining data the previous parse-error log did not print. Log a bounded description instead of the raw frame.

Suggested change
console.warn('Ignoring unrecognised WebSocket message:', text);
console.warn('Ignoring unrecognised WebSocket message');

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

usehoplite Bot and others added 2 commits October 1, 2026 08:02
…esperides-d9702ac5--parse-boundary

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>
…iefly

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>
@jR4dh3y
jR4dh3y merged commit 63ebabc into staging/main Oct 1, 2026
8 checks passed
jR4dh3y added a commit that referenced this pull request Oct 1, 2026
* Fix auto_discover mounts being dropped at startup (#131)

* Fix auto_discover mounts being dropped at startup

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Make the auto_discover test pick a mounted directory and check it is returned

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Make chunk_size_mb control browser upload chunks (#132)

* Make chunk_size_mb control browser upload chunks

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Share one chunk size request across upload workers and respect cancel

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Resolve virtual paths in one place (#133)

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Parse WebSocket and job data at the boundary (#134)

* Parse WebSocket and job data at the boundary

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Validate job progress range, add parser tests, log rejected frames briefly

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Move frontend state to runes and split the browse page (#136)

* Migrate jobs and websocket stores to runes

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Migrate settings store to runes and split out appearance helpers

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Migrate auth store to runes

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Split the browse page into composables

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Keep the WebSocket reconnect backoff when connect runs in an effect

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Fix the auth API docs, drop stale comments, add AGENTS.md (#137)

* Fix the auth API docs, drop stale spec comments, add AGENTS.md

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Add the frontend test command to AGENTS.md

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Correct the logout docs and scope the path rule

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Fix --dev failing config validation (#139)

* Fix --dev failing config validation

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Isolate the dev credentials test from inherited BoxBox settings

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Test the WebSocket reconnect backoff (#138)

* Test the WebSocket reconnect backoff

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Keep the connection mode on WebSocket retries and test the attempt limit

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Fix browsing discovered mount points

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Skip inaccessible auto-discovered mounts

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

This branch was successfully deployed

1 active deployment
Preview — 4d22dac9 Deployed Oct 1, 2026 by vercel[bot]
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