Files
Max Litruv Boonzaayer e058165830 feat: upgrade toolchain and soften sync recovery after network blips
Bump Vite 8, matrix-js-sdk, and related deps; fix immer/vanilla-extract imports for new majors. Replace blocking sync error dialog with background recovery on wake/offline, and disable Vite auto-open in dev. Add feature handoff documentation index.
2026-07-09 01:55:09 +10:00

4.0 KiB

Local HTTP API — handoff

Summary

Paarrot exposes a localhost HTTP API (port 33384) so external tools — Stream Deck, scripts, macros — can control the running desktop app: mute/deafen, switch rooms, send messages, and read status. The Electron main process runs an Express server; actions are forwarded to the renderer via IPC and executed against the live Matrix client and call service.

User-facing behavior

  • API starts automatically when the Electron app launches (no user setting).
  • Only binds to 127.0.0.1 — not reachable from the network.
  • No authentication today; security relies on localhost-only binding.

Architecture

External client (curl, Stream Deck, script)
  → GET/POST http://127.0.0.1:33384/...
    → electron/api-server.js (Express)
      → IPC to renderer (executeAction)
        → cinny/src/app/paarrot-api.ts (initPaarrotAPI)
          → Matrix client / CallService / router navigate

Navigation for room changes uses setPaarrotNavigate() registered from the React router in ClientNonUIFeatures.tsx.

Key files

Path Role
electron/api-server.js Express routes, IPC bridge to renderer
electron/main.js Starts PaarrotAPIServer with the main window
cinny/src/app/paarrot-api.ts Action handlers (mute, deafen, channel, message, status)
cinny/src/app/pages/client/ClientNonUIFeatures.tsx Calls initPaarrotAPI() and setPaarrotNavigate()
test-api.js / test-api.sh Smoke tests for endpoints
paarrot-api.postman_collection.json Postman collection (generated)

Data model

No persistent storage. Actions read/write ephemeral app state (current room, call mute/deafen).

IPC actions (renderer)

Action Params Returns
get-status mute, deafen, currentRoom, connected, userId
toggle-mute / set-mute { muted? } { muted }
toggle-deafen / set-deafen { deafened? } { deafened }
change-channel { roomId } navigation result
get-channels room list
send-message { roomId, message } send result
send-message-current { message } send to active room
get-current-room current room id

Dependencies

  • express, cors, body-parser (Electron)
  • matrix-js-sdk (renderer)
  • Call feature: getCallService() from features/call/useCall.ts

Integration points

  • Voice calls: mute/deafen actions delegate to CallService
  • Stream Deck: streamdeck-plugin/ consumes this API
  • Room navigation: uses getHomeRoomPath, getDirectRoomPath, getSpaceRoomPath
  • Space detection: isSpaceLikeRoom() checks im.paarrot.room.kind and m.forum type

Testing

Manual

  1. Start Paarrot desktop (npm run dev or built app).
  2. curl http://127.0.0.1:33384/health{ "status": "ok" }
  3. curl http://127.0.0.1:33384/status while logged in.
  4. Join a voice call; POST /mute/toggle and verify UI state.
  5. POST /channel with a room id; verify navigation.

Automated

  • node test-api.js from repo root

Known issues & gotchas

  • Mute/deafen only work when CallService has an active call; otherwise handlers return a message, not an error.
  • API is Electron-only; initPaarrotAPI no-ops when window.electron.api is missing (web/Tauri builds).
  • Adding endpoints requires changes in both api-server.js and paarrot-api.ts.

Future work

  • Optional auth token for local API
  • WebSocket or SSE for status push (Stream Deck polling today)
  • Document all endpoints in Postman collection generator (scripts/generate-postman-collection.js)

Add your extra things here

  • Default port: 33384 (PaarrotAPIServer constructor in api-server.js)
  • isSpaceLikeRoom treats forum_space room kind and m.forum create type as spaces for channel listing