# MWs (Middlewares) — Server Implementation Spec Client-side (`MwsShop` in Menu.jsx) is done. It expects the following from the server. Concept: a middleware is a named function that runs against a user's message text when they send it, transforming it (e.g. shrekify, uwuify) before it's broadcast. Users equip 0+ middlewares from a catalog; equipped ones are stored in order and applied as a chain. Equipping costs coins (`user.coins`, same currency shown in the user list as `₵{coins}`). Mirror the existing patterns already in the codebase: - `/channel/info/main/filteredWords` (GET, catalog fetch) → mirror for the MW catalog. - `/a/filter` (POST/DELETE) → mirror for equip/unequip semantics. - The `registered` live-flip described in `AccountPanel` (server pushes an updated `user` object over the socket after a state change, no reload needed) → do the same thing for `coins` and `middlewares` after equip/unequip/reorder. ## 1. Catalog: `GET /channel/info/main/middlewares` Returns the list of all middlewares available in the channel. ```json [ { "id": "shrek", "name": "Shrek", "description": "Ogre-ifies your text", "cost": 50 }, { "id": "uwu", "name": "UwU", "description": "uwuifies your text uwu", "cost": 25 } ] ``` - `id`: stable slug, used everywhere else (equip/unequip/reorder/message pipeline). - `cost`: coins deducted once, on equip. - No auth required to view the catalog (same as `filteredWords`). ## 2. User state: `user.middlewares` Add a `middlewares: string[]` field (array of catalog `id`s, in application order) to whatever object already carries `user.registered`, `user.coins`, `user.avatar`, etc. It needs to: - Be present in the initial `/preconnect` payload (or wherever `user` is first hydrated), defaulting to `[]`. - Be pushed live over the socket after equip/unequip/reorder, the same way `registered` flips live post-login — the client optimistically updates local state on a successful response, but the authoritative broadcast should match. ## 3. Equip: `POST /a/middleware/equip` Request: ```json { "channelName": "main", "id": "shrek" } ``` Behavior: - 401/`{ "error": "..." }` if not logged in / not registered (guests can't equip). - `{ "error": "Unknown middleware." }` if `id` isn't in the catalog. - `{ "error": "Already equipped." }` if `id` is already in `user.middlewares`. - `{ "error": "Not enough coins." }` if `user.coins < cost` (client also pre-checks this, but don't trust the client). - On success: deduct `cost` from `user.coins`, append `id` to the end of `user.middlewares`, persist, respond `{}`, and broadcast the updated user state (coins + middlewares) live. ## 4. Unequip: `POST /a/middleware/unequip` Request: ```json { "channelName": "main", "id": "shrek" } ``` Behavior: - `{ "error": "..." }` if not equipped / not logged in. - On success: remove `id` from `user.middlewares`, persist, respond `{}`, broadcast updated state. - **Refund policy is TBD** — client currently assumes no refund on unequip (cost is a one-time unlock). Flag this to product/design if that's wrong; easy to add a partial refund later without a client change (client only reads `user.coins` from the broadcast, doesn't compute deltas itself). ## 5. Reorder: `POST /a/middleware/reorder` Request: ```json { "channelName": "main", "order": ["uwu", "shrek"] } ``` Behavior: - `{ "error": "Invalid order." }` if `order` isn't a permutation of the caller's currently-equipped `middlewares` (same set, same length, no unknown/duplicate ids). - On success: set `user.middlewares = order`, persist, respond `{}`, broadcast updated state. - Order matters — see §6, middlewares are applied as a chain in this order. ## 6. Message pipeline (the actual point of the feature) When a registered user with a non-empty `user.middlewares` sends a chat message: 1. Look up each `id` in `user.middlewares`, in order. 2. Run the corresponding transform function against the message text, feeding each one's output into the next (i.e. a `reduce`). 3. Broadcast the *transformed* text to the room (this is what other users see; whether the sender also sees the transformed version or their raw input locally is a UX call — old implementation had it show the transformed text back to everyone including sender). 4. Each middleware's transform function lives server-side only; the client never sees implementation details, only `id`/`name`/`description`/`cost` from the catalog. 5. Middleware functions should be pure text→text transforms with no side effects, and should be defensive against pathological input (very long strings, no matches, etc.) — don't let one throw and drop the message. Example (from design chat): a user with `shrek` equipped sending `senpai ~` gets it transformed per the shrek middleware's rules before anyone (including themselves) sees it rendered in chat. ## Notes for parity with existing endpoints - All POST bodies are JSON, `Content-Type: application/json`, same as `/a/filter`, `/a/theme`, `/login`. - All error responses use `{ "error": "" }` with a non-2xx status or a 200 with the error field — check what `/a/filter` currently does and match it exactly for consistency (client's `.then(res => res.json())` code just checks `res.error`, doesn't branch on HTTP status). - Success responses can be empty-ish (`{}`), client doesn't require a body beyond checking for `.error`.