README.md: - Node prerequisite: v12+ → v22+ - Real-Time Updates: describe SSE push, remove polling/refresh-selector wording - On-demand mode: update for SSE connect triggering poll - API Endpoints: add /stream, /me, /csrf, /user-summary, /status, /cover-art - Remove stale /api/qbittorrent proxy entry - Docker tags: update to 1.0.x SECURITY.md: - Supported versions: add 1.0.x, retire 0.2.x - CSP header: add style-src-attr 'unsafe-inline' - Nginx example: add proxy_buffering off / proxy_read_timeout for SSE Diagrams: - seq-dashboard.puml: rewrite as SSE stream sequence (connect, initial payload, pushed updates, heartbeat, disconnect) - seq-polling.puml: add SSE subscriber notification step after cache population - state-ui.puml: replace Refresh Rate sub-state with SSE Connection state machine; update splash loading and logout transitions - state-poller.puml: add Notifying SSE subscribers step in Polling state package.json: bump to 1.0.0
94 lines
3.2 KiB
Plaintext
94 lines
3.2 KiB
Plaintext
@startuml seq-polling
|
||
!theme plain
|
||
title sofarr — Background Polling Cycle
|
||
|
||
participant "index.js\n(startup)" as entry
|
||
participant "Poller" as poller
|
||
participant "Config" as config
|
||
participant "SABnzbd\n(per instance)" as sab
|
||
participant "Sonarr\n(per instance)" as sonarr
|
||
participant "Radarr\n(per instance)" as radarr
|
||
participant "qBittorrent\nClient" as qbt
|
||
participant "MemoryCache" as cache
|
||
|
||
== Startup ==
|
||
entry -> poller : startPoller()
|
||
activate poller
|
||
|
||
alt POLL_INTERVAL > 0
|
||
poller -> poller : pollAllServices() (immediate)
|
||
poller -> poller : setInterval(pollAllServices,\nPOLL_INTERVAL)
|
||
else POLL_INTERVAL = 0
|
||
poller --> entry : "Polling disabled, on-demand mode"
|
||
end
|
||
|
||
== Poll Cycle ==
|
||
poller -> poller : Check: polling flag?\n(skip if concurrent)
|
||
poller -> poller : polling = true
|
||
poller -> poller : start = Date.now()
|
||
|
||
poller -> config : getSABnzbdInstances()
|
||
config --> poller : [{ id, url, apiKey }]
|
||
poller -> config : getSonarrInstances()
|
||
config --> poller : [{ id, url, apiKey }]
|
||
poller -> config : getRadarrInstances()
|
||
config --> poller : [{ id, url, apiKey }]
|
||
|
||
note over poller : All fetches run in\nparallel via Promise.all,\neach wrapped in timed()
|
||
|
||
par SABnzbd Queue
|
||
poller -> sab : GET /api?mode=queue
|
||
sab --> poller : { queue: { slots, status, speed } }
|
||
and SABnzbd History
|
||
poller -> sab : GET /api?mode=history&limit=10
|
||
sab --> poller : { history: { slots } }
|
||
and Sonarr Tags
|
||
poller -> sonarr : GET /api/v3/tag
|
||
sonarr --> poller : [{ id, label }]
|
||
and Sonarr Queue
|
||
poller -> sonarr : GET /api/v3/queue\n?includeSeries=true
|
||
sonarr --> poller : { records: [{ seriesId, series, ... }] }
|
||
and Sonarr History
|
||
poller -> sonarr : GET /api/v3/history\n?pageSize=10
|
||
sonarr --> poller : { records: [{ seriesId, ... }] }
|
||
and Radarr Queue
|
||
poller -> radarr : GET /api/v3/queue\n?includeMovie=true
|
||
radarr --> poller : { records: [{ movieId, movie, ... }] }
|
||
and Radarr History
|
||
poller -> radarr : GET /api/v3/history\n?pageSize=10
|
||
radarr --> poller : { records: [{ movieId, ... }] }
|
||
and Radarr Tags
|
||
poller -> radarr : GET /api/v3/tag
|
||
radarr --> poller : [{ id, label }]
|
||
and qBittorrent
|
||
poller -> qbt : getTorrents()
|
||
qbt --> poller : [{ name, progress, ... }]
|
||
end
|
||
|
||
poller -> poller : Record per-task timings\nlastPollTimings = { totalMs,\ntimestamp, tasks: [{label, ms}] }
|
||
|
||
poller -> poller : cacheTTL = POLL_INTERVAL × 3
|
||
|
||
poller -> cache : set('poll:sab-queue', ..., cacheTTL)
|
||
poller -> cache : set('poll:sab-history', ..., cacheTTL)
|
||
poller -> cache : set('poll:sonarr-tags', ..., cacheTTL)
|
||
|
||
note over poller : Tag queue records with\n_instanceUrl on embedded\nseries/movie objects
|
||
|
||
poller -> cache : set('poll:sonarr-queue', ..., cacheTTL)
|
||
poller -> cache : set('poll:sonarr-history', ..., cacheTTL)
|
||
poller -> cache : set('poll:radarr-queue', ..., cacheTTL)
|
||
poller -> cache : set('poll:radarr-history', ..., cacheTTL)
|
||
poller -> cache : set('poll:radarr-tags', ..., cacheTTL)
|
||
poller -> cache : set('poll:qbittorrent', ..., cacheTTL)
|
||
|
||
poller -> poller : Notify SSE subscribers\npollSubscribers.forEach(cb => cb())
|
||
|
||
note over poller : Each registered SSE client\ncallback rebuilds its payload\nand writes a data: frame
|
||
|
||
poller -> poller : polling = false\nlog elapsed time
|
||
|
||
deactivate poller
|
||
|
||
@enduml
|