The web client targets an older API: /mode only queries, numerics and member prefixes are not parsed, a 401 is retried forever #145

Closed
opened 2026-10-20 16:51:34 +02:00 by clawbot · 0 comments
Collaborator

web/src/app.jsx was written against an earlier shape of the API. Checked against the server on eceb5f2:

  • /mode sends {"command":"MODE","to":"#chan","params":["+m"]}. The server reads the change from body (handleChannelMode in internal/handlers/api.go), so a MODE with no body is a query: it answers 324 and 329 and changes nothing. The client's /help promises "Set channel modes"; no mode can be set from the web client.
  • processMessage matches numeric replies on "353", "332", "322", "323", "352", "315", "311", "312", "319", "318", "375", "372" and "376". The server sends the reply's name in command and its number in code (scanMessages in internal/db/queries.go; README.md, Numeric Reply Codes), for example {"command":"RPL_NAMREPLY","code":353,...}. None of the cases matches, and the default branch prints body alone. So /list prints each channel's topic without its name, and nothing for a channel with no topic; /who prints 0 nick per member; /whois prints the nick by itself; the @ and + prefixes in 353 are never read.
  • UserList groups members by m.mode ("o", "v"), but GET /api/v1/channels/{name}/members returns isOperator and isVoiced, so the member list never shows @ or +.
  • The poll loop treats a 401 as a network error and retries every 3 s for ever; after a QUIT from another client, or session expiry, the page shows "Reconnecting" indefinitely. README.md (Client Development Guide, Reconnection) says a 401 means a new session.
  • LoginScreen stores res.token, a field the session response does not have (README.md: no credential in the body), so localStorage holds the string undefined and every request carries Authorization: Bearer undefined. The client works only because the browser sends the cookie. Harmless, and worth removing in the same change.

Fix: send MODE as body: [change, value]; switch on msg.code; when reading 353 over the HTTP API take the nick before !, since the body carries hostmasks (README.md, 353 example); map isOperator and isVoiced to the prefixes; on 401 clear local state and show the login screen; drop the token code. The stored key is also what makes the page resume a session on reload (LoginScreen probes /state only when it is present), so the resume must then run unconditionally and fall through to the form on 401. The web build has no test runner, so the PR says how each item was checked in a browser against a server built from the branch.

Definition of done

  1. /mode +m on a channel the user operates sets the mode (the next /mode query shows it).
  2. /list, /who and /whois show channel names with counts, nick and flags, and the WHOIS lines, taken from the named numerics.
  3. Operators and voiced members show @ and + in the member list.
  4. A 401 on the poll returns the page to the login screen.
  5. No Authorization header and no neoirc_token in localStorage; a reload still resumes the session.
  6. One reviewed PR against next.

Model: fable-5-1

`web/src/app.jsx` was written against an earlier shape of the API. Checked against the server on `eceb5f2`: - `/mode` sends `{"command":"MODE","to":"#chan","params":["+m"]}`. The server reads the change from `body` (`handleChannelMode` in `internal/handlers/api.go`), so a MODE with no body is a query: it answers 324 and 329 and changes nothing. The client's `/help` promises "Set channel modes"; no mode can be set from the web client. - `processMessage` matches numeric replies on `"353"`, `"332"`, `"322"`, `"323"`, `"352"`, `"315"`, `"311"`, `"312"`, `"319"`, `"318"`, `"375"`, `"372"` and `"376"`. The server sends the reply's name in `command` and its number in `code` (`scanMessages` in `internal/db/queries.go`; `README.md`, Numeric Reply Codes), for example `{"command":"RPL_NAMREPLY","code":353,...}`. None of the cases matches, and the default branch prints `body` alone. So `/list` prints each channel's topic without its name, and nothing for a channel with no topic; `/who` prints `0 nick` per member; `/whois` prints the nick by itself; the `@` and `+` prefixes in 353 are never read. - `UserList` groups members by `m.mode` (`"o"`, `"v"`), but `GET /api/v1/channels/{name}/members` returns `isOperator` and `isVoiced`, so the member list never shows `@` or `+`. - The poll loop treats a 401 as a network error and retries every 3 s for ever; after a QUIT from another client, or session expiry, the page shows "Reconnecting" indefinitely. `README.md` (Client Development Guide, Reconnection) says a 401 means a new session. - `LoginScreen` stores `res.token`, a field the session response does not have (`README.md`: no credential in the body), so localStorage holds the string `undefined` and every request carries `Authorization: Bearer undefined`. The client works only because the browser sends the cookie. Harmless, and worth removing in the same change. Fix: send MODE as `body: [change, value]`; switch on `msg.code`; when reading 353 over the HTTP API take the nick before `!`, since the body carries hostmasks (`README.md`, 353 example); map `isOperator` and `isVoiced` to the prefixes; on 401 clear local state and show the login screen; drop the token code. The stored key is also what makes the page resume a session on reload (`LoginScreen` probes `/state` only when it is present), so the resume must then run unconditionally and fall through to the form on 401. The web build has no test runner, so the PR says how each item was checked in a browser against a server built from the branch. ## Definition of done 1. `/mode +m` on a channel the user operates sets the mode (the next `/mode` query shows it). 2. `/list`, `/who` and `/whois` show channel names with counts, nick and flags, and the WHOIS lines, taken from the named numerics. 3. Operators and voiced members show `@` and `+` in the member list. 4. A 401 on the poll returns the page to the login screen. 5. No `Authorization` header and no `neoirc_token` in localStorage; a reload still resumes the session. 6. One reviewed PR against `next`. Model: fable-5-1
clawbot self-assigned this 2026-10-20 16:51:34 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/neoirc#145