Desktop bridge
The website shows device status by talking to the desktop app over a small HTTP API on the loopback interface. The protocol is defined, with runtime schemas, in shared/src/bridge.ts. This page is the normative description; the desktop app must implement it exactly.
- Address:
http://127.0.0.1:47663(loopback only — never bind to0.0.0.0). - Protocol version:
1. Returned by/v1/hello; the website refuses to talk to other versions. - Format: JSON request and response bodies, UTF-8.
When the website connects
The website never contacts the bridge on page load. It connects only when:
- the person clicks Connect device on the dashboard, or
- this browser was paired before (a pairing token is saved in local storage), in which case the dashboard reconnects.
Browsers may show a permission prompt the first time a site talks to an app on the local machine (Chrome's Local Network Access). The website tells the person to expect it.
Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /v1/hello |
— | Identify the app. { app: "idlecash-desktop", protocol, version, deviceName } |
| POST | /v1/pair/start |
— | Begin pairing. Body { clientName }. Returns { requestId, expiresAt } and shows a 6-digit code in the desktop app |
| POST | /v1/pair/confirm |
— | Body { requestId, code }. Returns { token, deviceId } |
| POST | /v1/unpair |
Bearer | Revoke this browser's token |
| GET | /v1/snapshot |
Bearer | Current DeviceSnapshot |
| GET | /v1/earn-mode |
Bearer | { policy: EarnModePolicy } |
| PUT | /v1/earn-mode |
Bearer | Body { policy }. Validates, applies, returns the stored policy |
| GET | /v1/earnings |
Bearer | { status: "available", report } or { status: "unavailable", reason } |
Errors use one shape: { "error": { "code": <code>, "message": <text> } } where code is one of unauthorized, forbidden-origin, invalid-request, not-supported, pairing-expired, pairing-rejected, internal.
Pairing
Pairing proves that the person sitting at the computer approved this browser:
- The website calls
/v1/pair/start. - The desktop app shows a random 6-digit code in its own window. The code is never returned over HTTP.
- The person types the code into the website, which calls
/v1/pair/confirm. - The app returns a random bearer token (at least 128 bits) bound to the requesting origin.
The app should expire a pairing request after 2 minutes, allow at most 5 attempts per request, and let the person see and revoke paired browsers.
Security requirements for the desktop app
- Origin allowlist. Answer CORS requests only from the production website origin (and
http://localhost:3000in development builds). Reject everything else withforbidden-origin. - Private Network Access. Respond to preflights that include
Access-Control-Request-Private-Network: truewithAccess-Control-Allow-Private-Network: true— but only for allowed origins. - Host header. Reject requests whose
Hostisn't127.0.0.1:47663orlocalhost:47663, to defeat DNS rebinding. - Least privilege. The bridge exposes status and Earn Mode settings. It must never expose files, wallet secrets, or anything that could start arbitrary code.
- Validation. Treat every request body as untrusted. Clamp Earn Mode values to
EARN_MODE_RANGES(sanitizeEarnModePolicydoes this).
Capabilities
DeviceSnapshot.capabilities lists which Earn Mode controls the device can actually enforce (idle-detection, power-source, battery-level, gpu-usage, active-hours, cpu-limit, gpu-limit, storage-allocation, bandwidth-limit). The website shows a control only if the matching capability is present. Report a capability only when the platform implementation is real.
Measurements
Every measured value is nullable. Report null when something can't be measured, and the website shows "Not measured". Never estimate. In particular, bandwidth.availableMbps stays null until the app has run a measurement, with measuredAt set when it has.