321 lines
11 KiB
Plaintext
321 lines
11 KiB
Plaintext
Prompt 00 — Monorepo + shared protocol (TypeScript ovunque)
|
||
Crea una PR: “Monorepo bootstrap + shared protocol v1”.
|
||
|
||
Obiettivo:
|
||
- Impostare un monorepo con TypeScript ovunque e un pacchetto condiviso `shared/protocol` con tipi e helper per il protocollo WebSocket v1.
|
||
|
||
Struttura richiesta:
|
||
- /server (Node.js + TS)
|
||
- /windows (Electron + TS)
|
||
- /shared/protocol (TS, esporta tipi e validator base)
|
||
- /android NON in TS (verrà dopo), ma lascia spazio /android
|
||
|
||
Requisiti tecnici:
|
||
- Usa pnpm workspaces (preferibile) oppure yarn workspaces.
|
||
- In `shared/protocol` definisci:
|
||
- `ProtocolVersion = 1`
|
||
- tipi per tutti i messaggi Fase 1:
|
||
- agent_register, agent_registered, agent_heartbeat
|
||
- server_connect_request
|
||
- agent_connect_response
|
||
- client_login, client_login_result
|
||
- client_list_devices, client_device_list
|
||
- client_connect_request
|
||
- client_connect_status, client_connect_result
|
||
- error
|
||
- un `type AnyMessage = ...` union
|
||
- helper `isMessage(obj): obj is AnyMessage` con validazione minimale (no librerie pesanti; ok zod se vuoi ma tienilo semplice)
|
||
- helper `makeRequestId()` (uuid v4) e `nowTs()`
|
||
|
||
Output:
|
||
- Workspace funzionante con `pnpm i` e build TS.
|
||
- README root con comandi base.
|
||
|
||
Acceptance:
|
||
- `pnpm -r build` passa.
|
||
- Import da `shared/protocol` funziona da server e windows (anche se windows non è ancora implementato).
|
||
- Niente codice Android in questa PR.
|
||
|
||
Prompt 01 — Server skeleton + healthcheck + Docker
|
||
Crea una PR: “Server signaling TS skeleton + /health + Docker”.
|
||
|
||
Contesto:
|
||
- Monorepo esiste con `shared/protocol`.
|
||
|
||
Obiettivo:
|
||
- Creare `server` Node.js TypeScript che espone:
|
||
- HTTP GET `/health` => 200 “ok”
|
||
- WebSocket upgrade pronto (endpoints verranno dopo)
|
||
|
||
Requisiti:
|
||
- Framework HTTP: Fastify o Express (scegli uno e mantienilo semplice).
|
||
- Logger: pino (consigliato) o console strutturata.
|
||
- Config `.env` (usa dotenv).
|
||
- Dockerfile per server.
|
||
- Script: `pnpm --filter server dev` e `build` e `start`.
|
||
|
||
Acceptance:
|
||
- Avvio locale: `pnpm --filter server dev`
|
||
- `curl localhost:<port>/health` => 200
|
||
- `docker build` e `docker run` funzionano.
|
||
- Nessun endpoint WS richiesto ancora (solo scaffolding).
|
||
|
||
Prompt 02 — WebSocket endpoints /agent e /client + parsing robusto
|
||
Crea una PR: “WS endpoints /agent e /client + parsing robusto”.
|
||
|
||
Obiettivo:
|
||
- Implementare WebSocket su server con due path:
|
||
- ws://.../agent
|
||
- ws://.../client
|
||
|
||
Requisiti:
|
||
- Usa libreria `ws` (preferita) integrata con il server HTTP scelto.
|
||
- Per ogni connessione, identifica “kind” (agent/client) in base al path.
|
||
- Parsing JSON robusto: se arriva JSON invalido o messaggio non conforme, rispondi con:
|
||
{ v:1, type:"error", requestId: <se presente>, code:"BAD_REQUEST", message:"..." }
|
||
- Non deve crashare.
|
||
|
||
Acceptance:
|
||
- Connessione WS a /agent e /client possibile.
|
||
- Invio messaggio non JSON => ricevo error e connessione resta (o chiude con motivo, ok entrambe purché non crashi).
|
||
- Logga connect/disconnect con ip e path.
|
||
|
||
Prompt 03 — Auth Fase 1 (client login + agent pairingKey)
|
||
Crea una PR: “Auth F1: client_login + agent_register pairingKey”.
|
||
|
||
Obiettivo:
|
||
- Implementare autenticazione minimale:
|
||
- Client: `client_login` con username/password contro seed in env
|
||
- Agent: `agent_register` con pairingKey contro seed in env, associato allo stesso userId del seed
|
||
|
||
Env richieste (server):
|
||
- SEED_USERNAME
|
||
- SEED_PASSWORD (plaintext per ora, MVP)
|
||
- SEED_USER_ID (es. "user-1")
|
||
- SEED_PAIRING_KEY (string)
|
||
|
||
Comportamento:
|
||
- client_login ok => memorizza sessione in-memory (map ws -> userId).
|
||
- richieste client senza login => error UNAUTHORIZED.
|
||
- agent_register con pairingKey errata => error UNAUTHORIZED.
|
||
|
||
Usa i tipi di `shared/protocol`.
|
||
|
||
Acceptance:
|
||
- client senza login che fa list => UNAUTHORIZED
|
||
- login corretto => ok:true
|
||
- agent_register con pairingKey corretto => agent_registered ok:true
|
||
- agent_register con pairingKey errata => UNAUTHORIZED
|
||
|
||
Prompt 04 — Presence registry + heartbeat + lastSeen
|
||
Crea una PR: “Presence registry: devices online/offline + heartbeat”.
|
||
|
||
Obiettivo:
|
||
- Mantenere registry in memoria:
|
||
devicesById: deviceId -> { deviceId, deviceName, userId, online, lastSeenIso, wsRef? }
|
||
|
||
Regole:
|
||
- Su agent_register: crea/aggiorna entry, set online=true, lastSeen=now, salva wsRef.
|
||
- Su agent_heartbeat: aggiorna lastSeen=now (se deviceId noto), altrimenti error BAD_REQUEST.
|
||
- Su WS disconnect agent: online=false, lastSeen=now, wsRef=null.
|
||
|
||
Extra:
|
||
- Aggiungi un cleanup interval che marca offline se non riceve heartbeat entro 90s (anche se la socket resta “mezzo morta”).
|
||
|
||
Acceptance:
|
||
- Simulando heartbeat si aggiorna lastSeen.
|
||
- Chiudendo connessione agent => online=false.
|
||
- Se heartbeat manca >90s => online=false.
|
||
|
||
Prompt 05 — client_list_devices + filtro per userId
|
||
Crea una PR: “client_list_devices + device list filtrata”.
|
||
|
||
Obiettivo:
|
||
- Implementare:
|
||
- client_list_devices => server risponde client_device_list
|
||
|
||
Dettagli:
|
||
- Deve essere richiesto solo da client autenticato.
|
||
- Ritorna SOLO device associati al userId del client.
|
||
- Ogni device include: deviceId, deviceName, online, lastSeenIso.
|
||
|
||
Acceptance:
|
||
- Un client loggato vede la lista.
|
||
- Un client non loggato riceve UNAUTHORIZED.
|
||
- Se ci sono più device (simulati) appartenenti a userId diversi, ne vede solo i suoi.
|
||
|
||
Prompt 06 — Connect request routing + session state + timeout expired
|
||
Crea una PR: “Connect request routing + session state + timeout”.
|
||
|
||
Obiettivo:
|
||
- Implementare client_connect_request:
|
||
- verifica auth client
|
||
- verifica ownership (device.userId == client.userId)
|
||
- se device offline => error DEVICE_OFFLINE
|
||
- se device online => crea sessionId e status=pending
|
||
- inoltra a agent `server_connect_request` con {sessionId, fromUser: username}
|
||
- al client risponde `client_connect_status` status=pending
|
||
|
||
Session store in memory:
|
||
- sessionsById: sessionId -> { sessionId, userId, deviceId, status, createdAt, clientWsRef }
|
||
|
||
Timeout:
|
||
- dopo 60s se ancora pending => status=expired e invia al client `client_connect_result` status=expired
|
||
|
||
Acceptance:
|
||
- Richiesta verso device offline => DEVICE_OFFLINE
|
||
- Richiesta verso device online => agent riceve server_connect_request e client riceve pending
|
||
- Se agent non risponde entro 60s => client riceve expired
|
||
|
||
Prompt 07 — Agent accept/deny -> client result
|
||
Crea una PR: “agent_connect_response accept/deny -> client_connect_result”.
|
||
|
||
Obiettivo:
|
||
- Implementare agent_connect_response:
|
||
- valida sessionId esistente
|
||
- valida che session.deviceId == msg.deviceId
|
||
- valida che session.status == pending
|
||
- se decision accept => status=accepted
|
||
- se decision deny => status=denied
|
||
- invia a client `client_connect_result` con status accepted/denied
|
||
|
||
Edge cases:
|
||
- sessionId inesistente => error NOT_FOUND
|
||
- session non pending => error CONFLICT
|
||
- deviceId mismatch => error FORBIDDEN
|
||
|
||
Acceptance:
|
||
- Agent accept => client riceve accepted
|
||
- Agent deny => client riceve denied
|
||
- Risposte duplicate => CONFLICT
|
||
|
||
Prompt 08 — docker-compose dev (server + nginx reverse proxy WS)
|
||
Crea una PR: “Dev compose: nginx reverse proxy -> server (WS upgrade ok)”.
|
||
|
||
Obiettivo:
|
||
- Aggiungere `infra/docker-compose.yml` con:
|
||
- server
|
||
- nginx reverse proxy davanti
|
||
|
||
Requisiti:
|
||
- Nginx deve supportare WebSocket upgrade per /agent e /client.
|
||
- In dev, niente TLS (http + ws).
|
||
- Documenta in README come testare:
|
||
- ws://localhost/agent
|
||
- ws://localhost/client
|
||
(oppure con porte diverse, ma deve essere chiaro)
|
||
|
||
Acceptance:
|
||
- Connessione WS tramite nginx funziona (upgrade ok).
|
||
- /health raggiungibile via nginx.
|
||
|
||
Prompt Windows (Electron) — Fase 1
|
||
Prompt W1 — Electron skeleton + pagine login/devices
|
||
Crea una PR: “Electron TS skeleton: login page + devices page”.
|
||
|
||
Obiettivo:
|
||
- Creare app Electron in /windows con TypeScript.
|
||
- UI minimale con due schermate:
|
||
- Login (username/password, serverUrl)
|
||
- Devices (placeholder lista)
|
||
|
||
Requisiti:
|
||
- Puoi usare React+Vite oppure Electron Forge. Scegli stack semplice e moderno.
|
||
- Config serverUrl persistente (localStorage ok).
|
||
|
||
Acceptance:
|
||
- `pnpm --filter windows dev` avvia l’app.
|
||
- Navigazione login -> devices (anche finta).
|
||
|
||
Prompt W2 — WS client + client_login
|
||
Crea una PR: “Windows: WS client + client_login”.
|
||
|
||
Obiettivo:
|
||
- Connettere Electron a ws://<serverUrl>/client
|
||
- Implementare invio client_login e gestione client_login_result usando `shared/protocol`.
|
||
|
||
Requisiti:
|
||
- Gestisci reconnect base (retry ogni 2s fino a successo) o almeno errore UI.
|
||
- Dopo login ok => vai a schermata Devices.
|
||
|
||
Acceptance:
|
||
- Login corretto => ok e vai a Devices.
|
||
- Login errato => mostra errore.
|
||
|
||
Prompt W3 — Lista device (client_list_devices + refresh)
|
||
Crea una PR: “Windows: device list + refresh/poll”.
|
||
|
||
Obiettivo:
|
||
- Implementare:
|
||
- client_list_devices all’apertura della schermata
|
||
- poll ogni 5s (o pulsante refresh) per aggiornare
|
||
- Render tabella: deviceName, deviceId, online, lastSeenIso.
|
||
|
||
Acceptance:
|
||
- Vedo device comparire e cambiare stato online/offline.
|
||
|
||
Prompt W4 — Connect request UI + stato pending/accepted/denied/expired
|
||
Crea una PR: “Windows: connect request + status UI”.
|
||
|
||
Obiettivo:
|
||
- Aggiungere un bottone “Connect” per ogni device.
|
||
- Click => invia client_connect_request.
|
||
- Mostra stato:
|
||
- pending (spinner o testo)
|
||
- accepted / denied / expired
|
||
|
||
Requisiti:
|
||
- Gestisci più richieste (almeno una alla volta; se già pending, disabilita altri connect).
|
||
|
||
Acceptance:
|
||
- Pending appare subito.
|
||
- Alla risposta dell’agent si aggiorna la UI.
|
||
|
||
Prompt Android (Kotlin) — Fase 1
|
||
Prompt A1 — Foreground service + WS + deviceId persistente
|
||
Crea una PR Android: “Agent: ForegroundService + WS client + deviceId persistente”.
|
||
|
||
Obiettivo:
|
||
- App Android Kotlin con:
|
||
- ForegroundService “Remote Agent”
|
||
- WebSocket OkHttp
|
||
- deviceId UUID persistente in SharedPreferences
|
||
- schermata settings: serverUrl, pairingKey, deviceName (persistenti)
|
||
|
||
Comportamento:
|
||
- Avvio app -> start service -> connect ws://serverUrl/agent
|
||
- Log utile.
|
||
|
||
Acceptance:
|
||
- Service parte e resta in foreground con notifica.
|
||
- deviceId resta uguale tra riavvii app.
|
||
|
||
Prompt A2 — agent_register + heartbeat + reconnect
|
||
Crea una PR Android: “Agent: agent_register + heartbeat + reconnect”.
|
||
|
||
Obiettivo:
|
||
- Dopo WS open => invia agent_register {deviceId, deviceName, pairingKey}
|
||
- Heartbeat ogni 30s: agent_heartbeat
|
||
- Reconnect automatico se WS cade (retry con backoff semplice).
|
||
|
||
Acceptance:
|
||
- Server mostra device online.
|
||
- Stacco/riattacco rete => torna online senza riaprire app.
|
||
|
||
Prompt A3 — Notifica connect_request con azioni + agent_connect_response
|
||
Crea una PR Android: “Agent: notifica connect request + accept/deny”.
|
||
|
||
Obiettivo:
|
||
- Ricevere server_connect_request dal WS
|
||
- Mostrare notifica con azioni:
|
||
- ACCETTA
|
||
- RIFIUTA
|
||
- Click azione => invia agent_connect_response con {sessionId, deviceId, decision}
|
||
|
||
Requisiti:
|
||
- Deve funzionare anche con app in background.
|
||
- Usa PendingIntent + BroadcastReceiver per gestire azioni.
|
||
|
||
Acceptance:
|
||
- Notifica appare quando il client richiede connessione.
|
||
- Accept/deny inviano risposta e il client riceve l’esito. |