diff --git a/docs/diagrams/activity-matching.puml b/docs/diagrams/activity-matching.puml index b9d7718..a0d2598 100644 --- a/docs/diagrams/activity-matching.puml +++ b/docs/diagrams/activity-matching.puml @@ -2,6 +2,7 @@ !theme plain title sofarr — Download Matching Activity Diagram + start :Read cached data from MemoryCache; diff --git a/docs/diagrams/class-data.puml b/docs/diagrams/class-data.puml index e3631d1..aef074b 100644 --- a/docs/diagrams/class-data.puml +++ b/docs/diagrams/class-data.puml @@ -183,6 +183,14 @@ package "sofarr Internal Models" { + downloads : Download[] } + class "SSE Event\n/stream (data: frame)" as sser { + + user : string + + isAdmin : boolean + + downloads : Download[] + ' Same shape as /user-downloads response. + ' Pushed after every poll cycle completes. + } + class "Status Response\n/status" as statr { + server : ServerInfo + polling : PollingInfo @@ -212,6 +220,7 @@ package "sofarr Internal Models" { } apir *-- dl + sser *-- dl statr *-- si statr *-- pi } diff --git a/docs/diagrams/class-server.puml b/docs/diagrams/class-server.puml index 9d7eb87..7d0af2a 100644 --- a/docs/diagrams/class-server.puml +++ b/docs/diagrams/class-server.puml @@ -136,11 +136,14 @@ package "server/utils" { - polling : boolean - lastPollTimings : PollTimings|null - intervalHandle : number|null + - subscribers : Set -- + startPoller() : void + stopPoller() : void + pollAllServices() : Promise + getLastPollTimings() : PollTimings|null + + onPollComplete(cb) : void + + offPollComplete(cb) : void -- - timed(label, fn) : TimedResult } diff --git a/docs/diagrams/component.puml b/docs/diagrams/component.puml index 0d9cc9b..efd28e9 100644 --- a/docs/diagrams/component.puml +++ b/docs/diagrams/component.puml @@ -16,7 +16,7 @@ package "Browser" as browser { package "Express Server" as server { [index.js\nEntry Point] as entry - [app.js\ncreatApp() factory] as appfactory + [app.js\ncreateApp() factory] as appfactory package "Middleware" { [helmet\n(CSP nonce, HSTS)] as hm @@ -111,8 +111,8 @@ poller --> sonarr : tags + queue + history poller --> radarr : tags + queue + history qbt --> qbit : login + torrents/info -appjs --> auth : POST /login\nGET /me -appjs --> dashboard : GET /user-downloads\nGET /status +appjs --> auth : POST /login\nGET /me\nGET /csrf\nPOST /logout +appjs --> dashboard : GET /stream (SSE)\nGET /user-downloads\nGET /status es --> html : serve static @enduml diff --git a/docs/diagrams/seq-auth.puml b/docs/diagrams/seq-auth.puml index 8282fe3..7113dbe 100644 --- a/docs/diagrams/seq-auth.puml +++ b/docs/diagrams/seq-auth.puml @@ -54,14 +54,14 @@ alt Valid credentials Never sent to the client. 31-day TTL, atomic JSON write. end note - auth -> auth : Set emby_user cookie\n{ id, name, isAdmin }\nhttpOnly, sameSite=strict\nsecure (prod), signed (COOKIE_SECRET)\nrememberMe=true → Max-Age 30d\nrememberMe=false → session cookie + auth -> auth : Set emby_user cookie\n{ id, name, isAdmin }\nhttpOnly, sameSite=strict\nsecure (if TRUST_PROXY), signed (COOKIE_SECRET)\nrememberMe=true → Max-Age 30d\nrememberMe=false → session cookie auth -> auth : Generate csrfToken\n(32-byte random hex) - auth -> auth : Set csrf_token cookie\nhttpOnly=false (JS-readable)\nsameSite=strict, secure (prod) + auth -> auth : Set csrf_token cookie\nhttpOnly=false (JS-readable)\nsameSite=strict, secure (if TRUST_PROXY) auth --> browser : { success: true, user, csrfToken } browser -> browser : store csrfToken in memory browser -> browser : fadeOutLogin() browser -> browser : showDashboard() - browser -> browser : startAutoRefresh() + browser -> browser : startSSE() browser -> browser : dismissSplash() else Invalid credentials emby --> auth : 401 Error @@ -82,7 +82,7 @@ browser -> browser : store new csrfToken in memory == Logout == user -> browser : Click Logout -browser -> browser : stopAutoRefresh() +browser -> browser : stopSSE() browser -> auth : POST /api/auth/logout\n(no CSRF required — auth routes\nexempt; sameSite:strict protects) activate auth auth -> auth : Parse emby_user cookie → user diff --git a/docs/diagrams/seq-dashboard.puml b/docs/diagrams/seq-dashboard.puml index 15e6578..f004c2a 100644 --- a/docs/diagrams/seq-dashboard.puml +++ b/docs/diagrams/seq-dashboard.puml @@ -2,6 +2,7 @@ !theme plain title sofarr — Dashboard SSE Stream Sequence + actor User as user participant "Browser\n(app.js)" as browser participant "Express\n/api/dashboard" as dashboard diff --git a/docs/diagrams/seq-polling.puml b/docs/diagrams/seq-polling.puml index 7866218..28cdca4 100644 --- a/docs/diagrams/seq-polling.puml +++ b/docs/diagrams/seq-polling.puml @@ -2,6 +2,7 @@ !theme plain title sofarr — Background Polling Cycle + participant "index.js\n(startup)" as entry participant "Poller" as poller participant "Config" as config diff --git a/docs/diagrams/state-poller.puml b/docs/diagrams/state-poller.puml index a4a24e9..7fcb66f 100644 --- a/docs/diagrams/state-poller.puml +++ b/docs/diagrams/state-poller.puml @@ -2,6 +2,7 @@ !theme plain title sofarr — Poller State Diagram + [*] --> CheckConfig : startPoller() state CheckConfig <> diff --git a/docs/diagrams/state-ui.puml b/docs/diagrams/state-ui.puml index 6254602..c116f98 100644 --- a/docs/diagrams/state-ui.puml +++ b/docs/diagrams/state-ui.puml @@ -43,15 +43,12 @@ state Dashboard { state "Status Panel Closed" as status_closed [*] --> rendering - rendering --> rendering : SSE message received -→ renderDownloads() + rendering --> rendering : SSE message received\n-> renderDownloads() rendering --> rendering : Theme change - status_closed --> status_open : Click "Status" btn -(admin only) - status_open --> status_closed : Click close (×) - status_open --> status_open : 5s timer -→ renderStatusPanel() + status_closed --> status_open : Click "Status" btn\n(admin only) + status_open --> status_closed : Click close (x) + status_open --> status_open : 5s timer -> renderStatusPanel() [*] --> status_closed @@ -66,8 +63,6 @@ state Dashboard { } } -Dashboard --> LoginForm : Logout -(stopSSE, -clear state) +Dashboard --> LoginForm : Logout\n(stopSSE, clear state) @enduml