feat: Phase 1.5 — health prober, Oracle's Memory, off-thread EPG, PWA
Resilience + scale pass on top of the v1 core. Stream health prober: - New SSRF-validated /probe endpoint (HEAD -> ranged-GET fallback, 6s timeout, 5-min TTL cache, concurrency-capped, no-store) + orb_probe_total metrics - Front-end health.js: lazy bounded-concurrency probing (current + sampled channels, not the whole catalog), LIVE/DEGRADED/DEAD scoring, guide skull glyph, instant no-signal on zap to a known-dead channel Oracle's Memory (memory.js): - Session resurrection (last channel, volume, mute, theater/dim/ambient) - Exponential-decay per-channel uptime history; pinning; dead-channel auto-skip Off-thread XMLTV: - lib/xmltv-parse.js (pure, hand-rolled scanner, no DOMParser/regex backtracking) run in epg-worker.js module worker; wired into the iptv-org provider - Unit-tested in node: entity decode, TZ offsets, 20k programmes off-thread Service worker / PWA: - manifest.webmanifest + orb icons; sw.js (cache-first shell + hls.js, stale-while-revalidate for JSON-over-relay, never caches streams) - CSP gains worker-src + manifest-src; relay sends no-store on /relay + /probe so the SW is the sole metadata freshness authority; webmanifest MIME fix Docs: README de-emojified (title only) and reframed as a peer parallel build. Verified: SSRF probes blocked, legit probe + cache, new headers present, PWA assets served, parser tests 8/8, JS syntax clean, zero Rust warnings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<radialGradient id="orb" cx="42%" cy="38%" r="70%">
|
||||
<stop offset="0%" stop-color="#ff6a3d"/>
|
||||
<stop offset="32%" stop-color="#e10000"/>
|
||||
<stop offset="70%" stop-color="#6e0f0f"/>
|
||||
<stop offset="100%" stop-color="#1a0608"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#ff2b2b" stop-opacity="0.55"/>
|
||||
<stop offset="100%" stop-color="#ff2b2b" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- black field -->
|
||||
<rect width="512" height="512" fill="#050102"/>
|
||||
|
||||
<!-- ambient glow -->
|
||||
<circle cx="256" cy="248" r="220" fill="url(#glow)"/>
|
||||
|
||||
<!-- the orb -->
|
||||
<circle cx="256" cy="248" r="168" fill="url(#orb)" stroke="#ff2b2b" stroke-width="3"/>
|
||||
|
||||
<!-- scanlines clipped to the orb -->
|
||||
<clipPath id="c"><circle cx="256" cy="248" r="168"/></clipPath>
|
||||
<g clip-path="url(#c)" opacity="0.18">
|
||||
<g fill="#000">
|
||||
<rect x="88" y="92" width="336" height="3"/>
|
||||
<rect x="88" y="116" width="336" height="3"/>
|
||||
<rect x="88" y="140" width="336" height="3"/>
|
||||
<rect x="88" y="164" width="336" height="3"/>
|
||||
<rect x="88" y="188" width="336" height="3"/>
|
||||
<rect x="88" y="212" width="336" height="3"/>
|
||||
<rect x="88" y="236" width="336" height="3"/>
|
||||
<rect x="88" y="260" width="336" height="3"/>
|
||||
<rect x="88" y="284" width="336" height="3"/>
|
||||
<rect x="88" y="308" width="336" height="3"/>
|
||||
<rect x="88" y="332" width="336" height="3"/>
|
||||
<rect x="88" y="356" width="336" height="3"/>
|
||||
<rect x="88" y="380" width="336" height="3"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- highlight -->
|
||||
<ellipse cx="206" cy="190" rx="46" ry="30" fill="#ffd9d4" opacity="0.35"/>
|
||||
|
||||
<!-- wizard stand -->
|
||||
<path d="M176 410 L336 410 L304 372 L208 372 Z" fill="#1a0608" stroke="#6e0f0f" stroke-width="3"/>
|
||||
<rect x="150" y="410" width="212" height="20" rx="6" fill="#1a0608" stroke="#6e0f0f" stroke-width="3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
Reference in New Issue
Block a user