146 Commits

Author SHA1 Message Date
Timo 63db02211b fix(chat): harden overlay interactions and notifications 2026-07-26 04:14:25 +02:00
Timo 9251ae6aff add dedicated chat settings 2026-07-26 02:12:28 +02:00
Timo 77790a279c fix(chat): preserve mixed-version compatibility 2026-07-26 01:02:20 +02:00
Timo e9d735cd39 Merge remote-tracking branch 'origin/main' into feature/textchat
# Conflicts:
#	extension/background.js
#	extension/popup.js
2026-07-19 20:32:24 +02:00
Timo fe13275b5d Harden host access recovery races 2026-07-15 13:31:56 +02:00
Timo 142153a131 Harden host access recovery for v2.6.2 2026-07-15 12:46:57 +02:00
Timo 7e7e60f267 Fix blocked website access and add troubleshooting page 2026-07-15 12:24:59 +02:00
KoalaDev 9e0d294758 fix(chat): harden client state and messaging 2026-07-15 08:40:27 +02:00
KoalaDev e9dc299fc9 feat(chat): add in-page overlay 2026-07-15 07:23:02 +02:00
KoalaDev ee0d68d8d3 feat(chat): add client encryption and key lifecycle 2026-07-15 07:10:32 +02:00
Timo 9e8a446d97 fix: resolve Episode Lobby stuck loading state and prevent triggers on non-episodic media 2026-07-12 16:57:06 +02:00
KoalaDev 1f1844e99b Fix bamboo stalk taper and align stray colors to palette
The stalks used border-radius:50%, which renders a tall ellipse whose
ends taper to points, and unbounded parallax drift pushed them off the
bottom of the viewport on long pages. Rounded caps + 20% overscan +
a viewport-relative drift clamp keep them full-height while scrolling.

Sweep the remaining off-palette colors into the nature palette:
content-script overlays (slate grays), amber warnings/badges/star
accents (now terracotta) in popup, background badge and website
illustrations, and the support-heart red (now --danger).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 19:28:50 +02:00
Timo 869c64171b Fix Disney+ force sync, seek, and HCM regressions for v2.5.3
The v2.5.2 Disney+ page-API integration leaked blob-relative <video>
time into force sync, seeks, and heartbeats when the page-API bridge
had no fresh data, so force sync on Disney+ appeared broken.

- getSyncCurrentTime/getSyncDuration now refuse native values on Disney+
  (return null/0) so stale bridge data degrades to a clean no-op instead
  of broadcasting garbage to peers. The get_current_time handler and
  episode/lobby/hcmIsLive paths are routed through the same accessor.
- Validate FORCE_SYNC_PREPARE/SEEK payloads as finite before relaying;
  the internal coercion no longer treats '' as 0.
- Stop double-routing FORCE_SYNC_PREPARE from the popup path (the
  generic popup route now covers only play/pause/seek).
- popup force-sync: exclude null/empty peer times from the jump-to-others
  median (Number(null)===0 was dragging the target to 0), guard against
  NaN end-to-end, clear the dangling reset timer on failure, and retry
  without re-injecting when the content script responds but the Disney+
  bridge has not yet delivered a finite time.
- hcmIsLive skips the native-duration live signal on Disney+ only,
  preserving YouTube/Twitch Infinity-duration live detection.

Disney-specific logic remains strictly gated to disneyplus.com; no
Netflix/YouTube/Twitch/generic path is affected.
2026-07-02 16:57:48 +02:00
Timo 076157fef1 Sync Disney+ via the page media player API (precise time + seek)
Disney+'s <video> is blob-relative (unusable as an absolute clock) and its
scrubber aria-value freezes during playback, so DOM scraping lagged and the
+/-10s button seek could neither reach far targets nor land precisely. The
real player hangs off the <disney-web-player> custom element as
`.mediaPlayer`, exposing seek(ms) and timeline.info (playhead/duration ms).

Since the isolated content world can't read that page object, route it
through the existing MAIN-world page-API bridge (as Netflix already does):

- page-api-seek-overrides.js: register a 'disney' provider.
- background.js installPageApiSeekBridge: seek Disney via mediaPlayer.seek(),
  and post the exact playhead/duration (seconds) to the content world every
  250ms. Both are gated on provider === 'disney'; Netflix path unchanged.
- content.js: cache the pushed playhead, prefer it in getDisneyPlusTimeline
  (DOM scraping stays as fallback), and check the page-API seek first in
  seekVideo. Outcome is identical for Netflix and generic sites.

Verified live on Disney+: reported time matches the player exactly and
seek lands within ~1s of the target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 14:56:02 +02:00
Timo 51da169d69 Remove temporary Media Session interceptor and Video Event Log diagnostics
Clean up the exploratory Disney+ diagnostics now that the Shadow DOM
timeline/seek integration is in place:

- Remove Proposal 1 (Media Session interceptor): the background.js
  interceptor injection and the content.js message listener capturing
  __koalaMediaSessionCapture, plus the now-unused mediaSessionPosition
  reporting in content.js and popup.js.
- Remove Proposal 3 (Video Event Log): videoEventsLog, logVideoEvent(),
  the per-video event logging listeners, and the popup.html/popup.js UI.
- Keep Proposal 2 (DOM timestamp scraper) for ongoing diagnostics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 14:18:50 +02:00
Timo 066d2c9407 Implement temporary Media Session interceptor diagnostics for Disney+ and restore Netflix sessionId lookup guard 2026-07-02 13:19:47 +02:00
Timo ce522e2ab7 Safeguard Netflix player session ID lookup in background.js 2026-07-02 13:01:14 +02:00
Timo beda924b65 Fix DEV_SIMULATE_REMOTE_SEEK targetTime routing in background.js and add build identifier 2026-07-02 12:57:58 +02:00
KoalaDev 0af22998c8 Handle target tab navigation reinjection 2026-07-02 10:02:31 +02:00
KoalaDev 52265e84eb Generalize page API seek overrides 2026-07-02 09:57:57 +02:00
KoalaDev 57cd071d58 Add hidden remote seek test controls 2026-07-02 09:50:39 +02:00
KoalaDev 8a21fbe07f Fix Netflix seek handling 2026-07-02 09:43:11 +02:00
Timo fa7eea54d8 Split tab and media title privacy controls 2026-07-01 19:21:38 +02:00
Timo e555906553 fix(extension): harden title privacy edge cases 2026-06-29 13:43:10 +02:00
Timo 4f1335242c feat(extension): add shared title privacy modes 2026-06-29 13:35:34 +02:00
KoalaDev adab684ed0 perf+fix: debounce seq persistence + role changes, null-clear stale peer state
- server: SET_PEER_ROLE now uses the same per-room 500ms debounce as
  SET_CONTROL_MODE (M-4), preventing a buggy/malicious host from
  thrashing the room's UI with rapid promote/demote bursts.
- server: PEER_STATUS relay treats explicit null as 'clear' for
  tabTitle/mediaTitle/currentTime, so the tab-close heartbeat actually
  zeroes out stale state on other peers instead of being silently
  preserved by the clamp fallback.
- background: _persistLastSeq is now trailing-debounced (500ms),
  cutting storage.session IPC writes from one-per-relayed-event to
  one-per-quiet-window in active rooms.
- locales: add missing TOAST_ID_REGENERATED to all 14 non-English
  locales (was blocking locale coverage test on main).
- test: H-1 force-sync demote test now waits out the debounce window
  to reflect real-world UI timing (a host cannot promote, run a
  force-sync, and demote inside 500ms).
2026-06-29 01:22:41 +02:00
KoalaDev 77203fe8fd fix(extension): drop unused roomPassword field from GET_STATUS response
Defense-in-depth: the field was always undefined in practice (no code
path ever sets currentRoom.password — verified across the full git
history), and no GET_STATUS caller in the codebase reads it. But if a
future change ever populated currentRoom.password, any caller of
GET_STATUS would receive it in cleartext.

Threat surface today is narrow (externally_connectable is not set, no
content script sends GET_STATUS, only the popup uses it), but the field
has no reason to exist — removing it eliminates the future-leak vector
for free.

Backward compatible: no caller reads this field, so removing it is a no-op
for every existing code path.
2026-06-28 08:35:05 +02:00
KoalaDev 5379778a20 fix(extension): widen peerId generation to 16 hex chars (64-bit)
8 hex chars (32-bit) gives ~7% collision probability at the relay's
25k-peer capacity and ~1% at 10k peers. A same-room collision silently
triggers the dedup path and kicks the older session with a confusing
'Another session with this ID joined' error.

16 hex chars (64-bit) drops collision probability to ~1e-10 even at one
million peers. The server's JOIN_ROOM sanitizer already clamps peerId to
16 chars, so no server change is needed. Fully backward compatible:

- Existing clients keep their persisted 8-char peerId (only newly
  generated IDs change).
- Mixed 8-char + 16-char ID rooms work — both fit the server's 16-char
  clamp.
- No protocol change on the wire.

Birthday-paradox reference (computed, not estimated):
  32-bit @ 10k peers:  1.16%
  32-bit @ 25k peers:  7.02%
  64-bit @ 25k peers:  ~0.017e-9%
2026-06-28 08:29:59 +02:00
KoalaDev 17ac5c0a6f fix(host-control-mode): adversarial audit — force-sync stall, desync/lobby, gate parity, BC tests
Audit fixes (each verified against the actual code path):

H-1 (server): track force-sync initiator on PREPARE; let the demoted
initiator's EXECUTE through the host-only gate so mid-sync demotion no
longer strands the whole room paused. Clear on EXECUTE/peer-leave.

M-1 (background): episode-lobby gate now uses !amController() for parity
with CONTENT_EVENT and server gates — co-hosts can drive the room and
initiate lobbies, not just the owner.

M-2/M-3 (popup/content/background): forceSyncReset respects hcmGuestLocked;
desynced guest skips EPISODE_LOBBY so they don't get frozen in pause after
lobby completion, and checkEpisodeLobbyCompletion excludes desynced peers
from the required count so they don't block the lobby.

M-4 (background): getHostSyncTarget clamps extrapolation to 2x heartbeat
interval so a stale host heartbeat can't snap the guest tens of seconds
past the host's real position.

L-1..L-4 (server/content/background/popup): clarify dedup comment re:
network-blip window, enforce desync invariant on SW-restore, add
forceSyncBtn guest-locked backstop, refresh badge text in place.

Backward compatibility (verified by BC-1..BC-4 regression tests):
- Old client ↔ new server: server adds fields only, never requires; old
  heartbeats stripped of desynced; host-only enforced server-side even
  when the client has no awareness.
- New client ↔ old server: empty capabilities → host-control UI hidden,
  all gates default to everyone, behavior byte-identical to pre-HCM.
- Mixed rooms: every pre-HCM event type relays cleanly in both directions.
2026-06-28 05:54:50 +02:00
KoalaDev 7c65fe80cb feat(co-host): client gates use controller-set membership (background + content)
- background: track controllers[] from ROOM_DATA/CONTROL_MODE (restore + reset on
  teardown); add amController() (owner or co-host). Both gates now key on controller
  membership (sender: !amController(); receiver: senderId not in controllers); desync
  invariant uses amController. Thread controllers/amController/coHostSupported through
  GET_STATUS/GET_CONTROL_MODE/CONTROL_MODE. New SET_PEER_ROLE message (owner→server).
- content: gate on amController instead of amHost (a controller is not a gated guest);
  reconcile/HOST_BLOCKED updated accordingly. Drop now-unused hcmAmHost.

Single-host behavior unchanged (owner is always the sole controller until they promote).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 03:41:23 +02:00
KoalaDev dfa0a3d03e fix(host-control-mode): audit findings — host dedup demotion, episode-lobby freeze, +cleanups
Findings recovered from the multi-agent audit (verification phase was cut off by a
usage limit; verified inline against the code):

HIGH:
- Host was demoted on peerId dedup (fast reconnect / second tab): the dedup path
  removes the old socket and the kicked socket's 'disconnect' both ran the
  host-leave fallback before the same peerId re-joined → room silently unlocked on
  every host network blip. Now skip the fallback while a join for that peerId is in
  flight (peerJoinLocks). Regression test added. (A long real disconnect still
  falls back — that's the deferred host-grace EC-10.)
- Episode auto-advance froze a gated guest: in host-only the guest's EPISODE_LOBBY
  is dropped server-side, but the guest still self-paused (PAUSE_FOR_LOBBY) waiting
  for readies that never came → 60s freeze. A host-only guest now skips creating a
  lobby (the host drives episode sync).

LOW / cleanup:
- GET_HCM_STRINGS could return a raw key name ("HCM_DIALOG_TITLE") if the locale
  dictionary failed to load (getMessage returns the key on miss) → omit it so
  content keeps its English fallback.
- hcmReset now also clears the snap-back cooldown + buffering grace, so a stale
  cooldown can't swallow the first snap-back after a room/host change.
- Popup play/pause labels reset on lock too (not just unlock), so a button can't
  freeze on "Playing…" when host-only activates mid-click.
- Fix a stale comment (heartbeat now carries 'desynced').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 02:25:11 +02:00
KoalaDev 9963da2ebc feat(host-control-mode): explicit server capabilities for feature detection
Replace the implicit "hostPeerId present ⇒ feature supported" heuristic with an
explicit capabilities list the relay advertises in ROOM_DATA. Cleaner, self-
documenting, and the extensible hook for the planned co-host feature (owner
promotes guests to extra controllers) — a future 'co-host' capability + events
slot in without a protocol bump.

- shared: CAPABILITIES { HOST_CONTROL }.
- server: SERVER_CAPABILITIES advertised in ROOM_DATA.
- background: track serverCapabilities (empty against older relay), serverSupports(),
  thread hostControlSupported through GET_STATUS / GET_CONTROL_MODE / CONTROL_MODE.
- popup: gate the host-control card on the explicit capability instead of hostPeerId.

Backwards-compatible both ways: old relay omits the field → feature stays hidden
(no errors); old client ignores the field. WS test asserts ROOM_DATA advertises
the capability. Adds docs/host-control-mode-TESTING.md (beta-server setup, wss
caveat, two-new-clients note, verification checklist).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 04:48:45 +02:00
KoalaDev bb030a9737 feat(host-control-mode): localize the in-page dialog & badge
The host-control desync dialog and the "watching on your own" badge were
hardcoded English — content.js has no i18n loader. Background does, so it now
resolves the strings (GET_HCM_STRINGS: loadLocale + getMessage) and content
fetches them on init with English fallbacks, re-rendering the badge if it was
already shown. The dialog body is now generic (dropped the paused/jumped verb)
to keep translation clean across languages.

Adds 6 keys (HCM_DIALOG_TITLE/BODY/STAY/SOLO, HCM_BADGE_SOLO/RESYNC) translated
across all 15 popup locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 16:26:31 +02:00
KoalaDev 95ff460856 fix(host-control-mode): hold desync invariant + stop Solo-badge flicker
Two issues surfaced by a holistic audit of the combined desync-persist +
reconcile-on-init changes:

1. Stale desync outside gated-guest state. hcmDesynced is persisted, but neither
   ROOM_DATA nor CONTROL_MODE cleared it when the local peer became host or the
   room switched to 'everyone'. Combined with reconcile-on-init, a desynced guest
   promoted to host (host-leave fallback) while content was reloading could
   re-adopt desynced=true — self-labelling "Solo" to all peers and ignoring host
   commands in 'everyone' mode. Enforce the invariant centrally
   (hcmEnforceDesyncInvariant: desynced ⟹ host-only && !host) on every role/mode
   change, and guard the content reconcile to only adopt when actually a gated guest.

2. Solo-badge flicker. The host-side PEER_STATUS handler set peer.desynced
   unconditionally, but the background-driven keepAlive heartbeat omits the field —
   so every ~30s it clobbered desynced to false, flickering the badge. Only update
   when present (matching the sibling fields), and include desynced in the
   background heartbeat so both heartbeat sources are consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 16:06:04 +02:00
KoalaDev 2928393b4c fix(host-control-mode): reconcile desync state on content reinjection
Fixes a split-brain where a guest who chose "watch on my own" and then reloaded
the video page would appear stuck as "Solo" to the host while actually being
back in sync.

Root cause: background persists hcmDesynced (survives SW restart) and attaches
it to content-driven heartbeats, but a freshly injected content script started
with hcmDesynced=false and GET_CONTROL_MODE never returned the desync state — so
content re-applied host commands (synced) while background kept relaying
desynced=true to the host.

Fix: GET_CONTROL_MODE now also returns desynced; content adopts it on init
(re-shows the badge) and also adopts hostPeerId so later host-change resets work.
Desync now survives a page reload consistently on both sides, as intended.

Verified the full path: background.js heartbeat (1555 omits / 2018 attaches),
reset paths, CONTENT_BOOT and keepAlive-disconnect (neither reset it), content
sendHeartbeat on inject.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:45:33 +02:00
KoalaDev 5863991bfb fix(host-control-mode): remove redundant CONTENT_EVENT guard, persist desync in storage, block stale tab HCM_DESYNC_STATE
Original code at L1957 already validates sender.tab tabId against
currentTabId before calling processEvent() — added guard was
duplicate. HCM_DESYNC_STATE had no such guard; added with same
sender.tab && currentTabId pattern (popup-safe via undefined
short-circuit).

hcmDesynced now persisted in session storage:
- Restored in ensureState()
- Set on HCM_DESYNC_STATE change
- Reset to false in all 3 teardown storage.sets
Survives SW restarts; cleared on leave/switch/idle-room
2026-06-26 13:23:24 +02:00
KoalaDev 1eca3fa5bb fix(host-control-mode): guard against stale tab events, persist desync across SW restart
- CONTENT_EVENT now rejected from non-selected tabs (popup exempted via sender.tab check)
- HCM_DESYNC_STATE same guard
- hcmDesynced persisted in session storage: restored on SW restart, cleared on teardown
2026-06-26 13:18:09 +02:00
KoalaDev 58af258fd3 fix(host-control-mode): harden gates, close UX races, debounce toggles, surface desync state
Bis auf peerId-Auth (akzeptiert) alle Audit-Findings behoben:
- Snap-back spielt nicht mehr blind ab, wenn Host-Status unbekannt
- Teardown-Pfade broadcasten jetzt CONTROL_MODE → kein verwaistes Badge/Dialog
- Dialog-Timer bei Ersetzung sauber gecleart
- Server re-synchronisiert Sender bei nicht-Host-Reject
- Popup-Toggle revertiert bei Server-Ablehnung
- Desync-Zweig sendet keine Phantom-ACKs mehr an Host
- Toggle-Debouncing serverseitig (500ms pro Raum)
- Button-Text auf Unlock zurückgesetzt
- Live-Erkennung defensiver, Resync-Retry bei fehlendem Target, Badge-Retry

Neu: Host sieht 'Solo'-Badge für desyncte Gäste via PEER_STATUS-Heartbeat
(16 Sprachen, neuer i18n-Key BADGE_DESYNCED/TOOLTIP_PEER_DESYNCED)

Tests: H-5-Reject-Unicast, M-4-Debounce, Desync-Heartbeat-Relay
2026-06-26 13:09:34 +02:00
KoalaDev f9dc9d4017 feat(extension): host control mode guest UX in content script (step 4)
Guest-side handling when a room is in host-only mode:

- Intent classifier (EC-9): tells a deliberate guest pause/seek from an
  involuntary one (buffering/seeking/tab-refocus/no recent gesture) via
  readyState, video.seeking, a 'waiting' buffering-grace window, the existing
  visibility grace, and own-tracked keydown/pointerdown gestures.
- HOST_BLOCKED handler: live streams are left alone (EC-15); involuntary events
  snap back silently; deliberate events show a Teleparty-style choice dialog
  (Stay in sync / Watch on my own), defaulting to stay after 8s (EC-18).
- Snap-back reuses tryMediaAction + suppress, with a cooldown to avoid
  pause/play fight loops (EC-4).
- Desync ("watch on my own") mode: persistent resync badge, ignores host
  commands while solo (still ACKs so force-sync doesn't stall), and auto-clears
  on episode change (EC-12). Resync pulls the host's extrapolated position.
- background: GET_CONTROL_MODE (init fetch) + REQUEST_HOST_SYNC (resync target).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 04:10:15 +02:00
KoalaDev 7b37d783b5 feat(extension): host control mode wiring in background (step 3)
Background service worker now understands room control mode (no UI yet):

- Track controlMode + hostPeerId + amHost(); adopt from ROOM_DATA, update on
  CONTROL_MODE, restore from session storage, reset on leave/room-switch.
- Receiver-side gate: ignore room-moving events from non-host peers in host-only
  mode (backstop for old/buggy/modified clients past the server gate).
- Sender-side gate: a guest's play/pause/seek/force-sync is not broadcast;
  instead a HOST_BLOCKED message is sent to the content tab with an extrapolated
  host sync target (hook for step 4's snap-back + desync dialog).
- broadcastControlMode() notifies popup + content tab; GET_STATUS exposes
  controlMode/hostPeerId/amHost; new SET_CONTROL_MODE message lets the host toggle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 04:04:54 +02:00
KoalaDev 6276ee134c fix(ui): Correct false idle state on connection errors
Bump version to 2.4.6
2026-06-23 18:36:05 +02:00
KoalaDev bd60a14754 feat(extension): send nothing to the server while alone in a room
When no other peer is present, heartbeats, force-sync, and episode auto-sync
are now fully suppressed (previously the keepAlive heartbeat, force-sync, and
episode lobby were still broadcast to an empty room):
- keepAlive heartbeat gated on a live peer count
- force-sync is a no-op when solo (no pause-then-timeout freeze, no traffic)
- episode auto-sync skips the lobby entirely and just plays the next episode

The solo state is recomputed live from the current peer list on every event
(never cached), so the instant another peer joins syncing resumes. On that
solo->not-solo transition the background also requests an immediate heartbeat
from the content script so the newcomer sees the current position without
waiting for the next interval.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:38:36 +02:00
KoalaDev a1bdcf4325 fix(extension): stop storing room/settings in chrome.storage.sync
Room ID, password, and username were resurrected from synced storage on a
fresh install (sync survives an uninstall in the user's Google account), which
made the extension silently auto-connect to a dead room and appear permanently
connected. getSettings() and all settings reads (popup, content, audio-options,
applyAudioSettingsToTab) are now local-only, and legacy keys are actively
purged from sync on install/update/startup. Only onboardingComplete and
dismissedHints remain in sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:38:36 +02:00
KoalaDev b450845522 fix(extension): harden reconnect, ping, and offline-queue flush
Address client-side rate-limit edge cases that could trip the server's
per-socket event budget or per-IP connection limit:

- Pace the offline event-queue flush after (re)connect in batches
  (10 per 3s) instead of one synchronous burst, so a reconnect after a
  long outage no longer dumps the whole backlog and gets kicked.
- Wrap socket.send() in try/catch and re-queue on failure (race with a
  server-side disconnect) instead of losing the event.
- Ping liveness: tolerate one missed PONG; only force a reconnect after
  2 consecutive misses (~20s) instead of a single hard 5s timeout.
- Reconnect backoff: tune to ~8 attempts/60s (under the connection limit)
  and add +/-20% jitter to de-synchronize reconnect herds.

All magic numbers extracted into named constants.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:45:29 +02:00
KoalaDev d2c8ca1c1e Merge PR #11: extract tab management logic into independent module
3-way merge of Kaia-Alenia's tab-manager refactor (PR #11) onto current
main (v2.4.3). Only the tab-related service-worker listeners move out of
background.js into extension/modules/tab-manager.js via initTabManager({...})
dependency injection; all other recent main changes are preserved untouched.
Behavior-preserving; listeners still register synchronously at top level.

The unrelated website/assets/NewLogoIcon_64.webp change from the PR was
excluded to keep main clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 02:06:38 +02:00
Kaia-Alenia 1c404a7f11 refactor(extension): extract tab management logic into independent module 2026-06-19 14:11:47 -06:00
Timo 23d43741ce chore: replace Ko-Fi with Support links, drop uninstall token
Switch the Ko-Fi support links to https://support.koalastuff.net across the
README badge, the extension footer (popup.html/template.html, label
"Support KoalaSync") and remove the now-unused KOFI_URL constant. Stop
generating/sending the anonymous uninstall token: initUninstallURL() now
registers the feedback URL without the `t` parameter or storage write.
CHANGELOG updated to match.

Also guard the client CMD_ACK path: only ACK a command sender that is still
a known peer in the room, so we don't emit ACKs to peers that already left
(which the server now drops as absent-peer ACKs anyway).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 16:26:54 +02:00
KoalaDev 32fd677a31 feat: optimize extension uninstall URL registration with anonymous token and startup hardening 2026-06-19 11:54:07 +02:00
Timo 6c96dd6344 chore(refactor): remove orphaned comment, add double-start guard to rate-limiter 2026-06-16 13:10:37 +02:00
Timo d7bb8dc97c refactor(extension): extract episode-utils from background.js + content.js
Move extractEpisodeId() and sameEpisode() to shared episode-utils.js.
background.js imports via ES module. content.js (IIFE) receives injected
copy via build-extension.js marker replacement, eliminating duplication.
2026-06-16 12:48:11 +02:00