Files
KoalaSync/extension
Timo 226453fd45 fix(extension): release a dead frame election and break the discovery deadlock
Reported as "Receiving end does not exist" with targetReady true and no
activation errors: the election named a frame the player had already torn down.
Three defects were stacked, each hidden by the one in front of it.

The election was never released. getReadyTabVideoState() recovered through the
guarded refresh, which reports "unchanged" when no video is reachable, so the
stale frameId/documentId survived; adoption compounded it by setting hasVideo,
after which the target only moves on a frame change. An unreachable content
script — as opposed to a page that simply has no video yet — now releases the
frame election back to the top frame. The tab selection is never touched.

Switching frames destroyed the top frame's scripts. Promoting the target out of
frame 0 called deactivateTargetTab() on the previous target, which sent
TARGET_DEACTIVATE to frame 0 and tore down its content script and the chat
overlay with it. That is why chat delivery failed after promotion, and why
releasing the election pointed at an empty frame. An in-tab frame switch now
leaves the top frame alone.

Discovery could deadlock. Monitors announce new players, but a rebuilt frame is
a new document with no monitor, so the video created in it was never reported —
and nothing then triggered the upkeep that would have installed one. Monitors
are reinstalled on every lifecycle notification with a trailing-edge debounce,
and a bounded discovery poll breaks the cycle when no notification arrives at
all: 2s, capped, only while a tab is selected with no video found, stopping the
moment one is.

The new browser test adopts a nested player, destroys its document the way the
real player does, and asserts both the release and that the rebuilt player is
picked up again without touching the popup. It was flaky until the deadlock was
closed, and now passes repeatedly at a stable 8.3s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 20:43:58 +02:00
..
2026-06-04 15:33:06 +02:00

KoalaSync Browser Extension

This directory contains the Manifest V3 browser extension for Chrome and Firefox. It owns the popup UI, background service worker, content-script video control, invitation bridge, audio processing, and all browser-local settings.

Where You Are

  • manifest.base.json is the source manifest. The build script creates browser-specific manifest.json files in dist/chrome/ and dist/firefox/.
  • background.js is the long-lived coordinator: WebSocket client, room state, host-control authority, heartbeat, reconnects, tab selection, and content-script injection.
  • content.js runs in the selected video tab. It detects video state, applies remote play/pause/seek, handles episode transitions, and applies local audio processing.
  • popup.html and popup.js implement the visible extension UI.
  • extension/shared/ is generated by npm run build:extension from the root shared/ directory. Do not edit it directly.

Key Features

  • Manifest V3: Service-worker architecture with session persistence and explicit keep-alive handling.
  • Pure Vanilla JS: No extension runtime dependencies and no bundler inside extension/.
  • On-Demand Connection: The service worker connects only while the user intends to be in a room.
  • Host Control & Co-Hosts: Hosts can switch a room into host-only mode and grant controller rights to trusted peers.
  • Episode Auto-Sync: Title/episode changes can open a lobby so peers resume together once everyone is ready.
  • Smart Matching & Title Privacy: Matching video tabs are highlighted, while tab/media title sharing can be reduced or disabled.
  • Audio Processing: Optional local compressor settings live in the dedicated audio options page.
  • Status Diagnostics: The Status tab exposes connection state, ping, video debug data, action history, and copyable logs.
  • Dynamic i18n: 15 languages are supported: en, de, fr, es, it, nl, pl, pt, pt-BR, tr, ru, ja, ko, zh, and uk.

Tab Overview

  1. Room: Select official/custom server, create or join rooms, view peers, share invite links, and manage Host Control when supported by the relay.
  2. Sync: Select the video tab, send play/pause/seek/force-sync actions, and view episode lobby state.
  3. Settings: Configure username, title sharing, noise filtering, auto episode sync, notifications, language, and audio options.
  4. Status: Inspect connection state, latency, video debug info, history, and logs for bug reports.
  5. Dev: Hidden developer-only controls shown for the KoalaDev username.

Privacy & Permissions

KoalaSync requires <all_urls> host permission so it can detect and control <video> elements on arbitrary sites.

  • No browsing history is collected or uploaded.
  • Room credentials and user settings are stored locally with chrome.storage.
  • No analytics, external scripts, external fonts, or tracking libraries are used by the extension.
  • Audio processing is local to the selected tab.
  • Title privacy controls decide whether tab/media titles are shared with room peers.

Installation

From the repository root:

npm install
npm run build:extension

Then load the generated bundle:

  • Chrome/Chromium: open chrome://extensions/, enable Developer Mode, and load dist/chrome.
  • Firefox: open about:debugging, choose This Firefox, and load dist/firefox/manifest.json.

Development

Run the build whenever shared protocol files or extension packaging inputs change:

npm run build:extension

The build copies shared/constants.js, shared/blacklist.js, shared/names.js, and shared/README.md into extension/shared/, injects synchronous constants into content.js, generates browser manifests, and creates zip artifacts in dist/.

Useful focused checks from the repository root:

node -c extension/background.js
node -c extension/content.js
node -c extension/popup.js
node scripts/test-episode-utils.mjs
node scripts/test-title-privacy.mjs
node scripts/test-audio-settings.mjs
node scripts/test-locales.cjs

For the full suite, run:

npm run verify

Do Not Break

  • Keep content.js synchronous and IIFE-based; it cannot import ES modules directly.
  • Keep the injection markers used by scripts/build-extension.cjs.
  • Keep protocol names in shared/constants.js as the source of truth.
  • Keep extension runtime dependencies at zero unless the project explicitly decides to introduce a bundler.
  • Keep all extension assets self-hosted.

Module Structure

File Purpose
background.js Service worker: WebSocket protocol, room state, host control, tab/content routing, reconnects
content.js Video detection/control, audio processing, episode transition, host-only guest behavior
popup.js Popup UI: room join/create, tabs, settings, peer list, status, diagnostics
popup.html Popup markup, tabs, onboarding, status/debug surfaces
bridge.js Invitation bridge injected into sync.koalastuff.net
episode-utils.js Shared episode-title parser imported by background and injected into content at build time
title-privacy.js Tab/media title privacy modes and sanitization helpers
audio-options.html / audio-options.js / audio-options.css Dedicated local audio-processing settings page
page-api-seek-overrides.js Page-level seek bridge for site-specific player APIs
modules/tab-manager.js Tab lifecycle helper used by the background service worker
i18n.js Dynamic locale loader and DOM translation helper
locales/ Runtime popup translations
_locales/ Browser-store manifest translations
shared/ Generated mirror of root shared constants, blacklist, names, and README