Commit Graph

75 Commits

Author SHA1 Message Date
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 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 9f4be58172 feat(server): host control mode — room state, gate, toggle & host fallback
Step 2 of host control mode (server-side, fully testable without UI):

- Room now tracks hostPeerId (= first joiner) and controlMode ('everyone' default).
- ROOM_DATA carries hostPeerId + controlMode so clients know their role on join.
- SET_CONTROL_MODE handler: host-only authority check, broadcasts CONTROL_MODE.
- Relay gate: in host-only mode, drop room-moving events (play/pause/seek/
  force-sync/episode-lobby) from non-host guests — robust chokepoint that kills
  spam regardless of client. Heartbeats/ACKs/episode-ready still pass.
- removePeerFromRoom: if the host leaves, fall back to 'everyone' and reassign
  host to the earliest remaining peer (v1: immediate, no grace period).

Extends the WS integration test suite to cover all of the above.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:30:57 +02:00
KoalaDev 27023ea58e feat(server): raise event rate limit to 50 and name rate-limit constants
Raise the per-socket event budget from 30 to 50 per 10s to give legitimate
bursts (episode joins, force-sync, ACK flurries) more headroom. Extract the
previously inline connection/event/health/admin windows and limits into named
constants (CONNECTION_RATE_LIMIT, EVENT_RATE_LIMIT, *_WINDOW_MS). Tests now
derive their thresholds from the exported constants instead of hardcoding them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:45:19 +02:00
Timo 027d2dbde5 fix(sync): coalesce play/pause bursts and quiet absent-peer ACK logs
Media players fire bursts of native play/pause events (source swaps, ABR,
ads, teardown). Each was relayed as a distinct command, spamming peers and
tripping the relay's event rate limit. Add leading+trailing coalescing in
the content script: emit the first event instantly (zero added latency) and,
on a 150ms window, collapse a burst to its final state. Echo-suppression and
seek-flush stay synchronous on event arrival; the trailing send is never
deduped against the leading one (a remote command may change shared state
mid-window — re-sending is the safe, idempotent choice).

Server: split the EVENT_ACK handler's logging so only a genuine different-room
ACK is logged as [SECURITY]; an ACK to a peer that already left is logged
quietly as [ACKDROP] (verbose-only), so real signals are no longer drowned out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 16:26:31 +02:00
Timo 101761e984 chore: add type:module, fix npm audit, rename CJS files, document module structure
- package.json: add type: module to silence ESM warnings
- server: npm audit fix (0 vulnerabilities, ws vuln resolved)
- Rename 4 CJS files to .cjs: build-extension, test-content-video-finder,
  test-locales, website/build
- Update eslint.config.mjs for .cjs glob patterns
- extension/README.md + server/README.md: document module structure
2026-06-16 13:22:45 +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 d38a840114 refactor(server): extract rate-limiter module from index.js
Move 6 rate-limit functions, all rate-limit Maps, cleanup intervals,
and denial counter to server/rate-limiter.js. 149 lines extracted.
Index.js re-exports what tests and ops.js need.
npm run verify passes.
2026-06-16 12:44:43 +02:00
KoalaDev dd8eefe3f9 fix(security): actually prevent timing leak — eagerly evaluate timingSafeEqual
The previous fix had a subtle bug: JavaScript && short-circuits,
so crypto.timingSafeEqual() was skipped when buffer lengths differed
(sameLength was false). The dummy buffer was allocated but never
compared, leaving the original length-based timing leak intact.

Now timingSafeEqual is eagerly assigned to a const before the &&
guard, guaranteeing it runs in constant time on every auth attempt
regardless of whether the length guess was correct.
2026-06-16 04:23:20 +02:00
KoalaDev cc97e0d371 fix(security): prevent admin token length leak via timing side-channel
isAdminMetricsAuthorized() returned early when buffer lengths differed,
allowing an attacker to discover the token length by measuring response
time. Now always calls crypto.timingSafeEqual (using a zeroed dummy
buffer on length mismatch) so every auth attempt takes constant time
regardless of whether the length guess was correct.

Reported-by: Kaia-Alenia
2026-06-16 04:21:15 +02:00
Timo d76e9195c4 fix(server): race condition on concurrent peer joins, crash-safe teardown, smart unhandled rejection handling
- Add per-peerId serialization lock (peerJoinLocks) to prevent concurrent dedupe races

- Wrap removePeerFromRoom calls in disconnect/leave/reaper with try/catch

- Replace immediate process.exit on unhandledRejection with rate-limited smart exit

- Optimize buildHealthPayload from 3-pass array ops to single for-of loop

- Reset rateLimitDenied counters in stopServerForTests

Release v2.3.1
2026-06-15 13:14:00 +02:00
Timo 6ba5e1b10b Increase failedAuthAttempts eviction limit from 50k to 200k 2026-06-13 04:38:22 +02:00
Timo 131afadc1d v2.2.4: Fix notification setting bypass and misleading reconnect message
- EVENTS.ERROR handler now respects browserNotifications setting
- Graceful shutdown message no longer implies manual reconnect
2026-06-10 01:33:45 +02:00
Timo 34f0c2b265 chore(release): v2.2.3 — artifact attestations, bugfixes, rate limit metrics 2026-06-10 00:56:29 +02:00
KoalaDev 3e7afd7592 feat: add server ping display with peer ping foundation
Adds application-level ping/pong between extension and relay server.
Extension sends PING every 15s, server echoes PONG. Round-trip time
displayed in Status tab (green/yellow/red color-coded).

Server also forwards PING with target peerId and routes PONG back,
enabling future peer-to-peer ping without server restart.
Extension already responds to incoming peer PINGs.

See CHANGELOG.md for details.
2026-06-09 07:08:14 +02:00
KoalaDev da12dc07a7 move example configs to examples/ dir 2026-06-07 12:55:43 +02:00
Koala ad33720053 feat(server): add DEBUG_LOGGING environment variable to suppress verbose console output 2026-06-03 11:57:58 +02:00
Koala fa3341cc8e docs(server): quote values in env.example and set empty token 2026-06-03 11:51:49 +02:00
Koala a6be6b2670 perf(server): optimize failedAuthAttempts LRU eviction to O(1) 2026-06-03 11:44:06 +02:00
Koala 595ea297f5 chore(release): release v2.0.5 2026-06-03 11:33:54 +02:00
Koala 4f4cdcd8c0 Cache health responses server-side 2026-06-03 11:04:45 +02:00
Koala 602be3a724 Tighten health endpoint hardening 2026-06-03 10:59:08 +02:00
Koala 81c50eff16 Harden room discovery and health metrics 2026-06-03 10:48:49 +02:00
Koala 3ec9812265 fix: use server-trusted peerId in relay payload to prevent spoofing 2026-06-02 08:17:08 +02:00
KoalaDev 2f11c60307 Merge pull request #2 from Shik3i/dependabot/npm_and_yarn/server/npm_and_yarn-e5a46ec0e1
chore(deps): bump ws from 8.18.3 to 8.20.1 in /server in the npm_and_yarn group across 1 directory
2026-06-02 07:41:19 +02:00
dependabot[bot] 030b839b12 chore(deps): bump qs
Bumps the npm_and_yarn group with 1 update in the /server directory: [qs](https://github.com/ljharb/qs).


Updates `qs` from 6.15.1 to 6.15.2
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.15.1...v6.15.2)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.15.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 05:39:05 +00:00
dependabot[bot] 5c805bcafa chore(deps): bump ws
Bumps the npm_and_yarn group with 1 update in the /server directory: [ws](https://github.com/websockets/ws).


Updates `ws` from 8.18.3 to 8.20.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.18.3...8.20.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.20.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 05:39:00 +00:00
Timo 1a7ff6b3a7 docs: shorten compose example link text in readme 2026-06-01 12:24:09 +02:00
Timo b1b858d771 docs: replace single docker-compose example with caddy + static ip variants 2026-06-01 12:16:25 +02:00
Timo f7096edd30 Refactor: Behebung Force-Sync ACK-Zaehler-Inflation, SHA-256 Hashing, Room-ID-Desinfizierung, Reduzierung MIN_SEEK_DELTA auf 2s und seekDebounce auf 300ms 2026-05-30 02:00:17 +02:00
Timo 93acd0b44c fix: resolve F-01 to F-08 audit bugs and QoL improvements 2026-05-30 01:34:48 +02:00
Timo 56955027f9 Fix critical sync lockouts, zombie disconnects, play-seek debounces, active lobbies, SW keep-alives, branding matches and ESLint warnings 2026-05-30 00:02:19 +02:00
Koala 98b4fc5fb4 release: v1.9.0 — command sequencing, episode-aware sync, echo suppression
- Add per-peer monotonically increasing seq numbers (localSeq + lastSeqBySender)
- Server relays seq for stale command detection (backward compatible)
- Replace expectedEvents (1500ms timeout) with _suppressTimers (per-type, 300ms)
- Fix FORCE_SYNC_ACK missing seq (stale-ACK guard)
- Fix episode lobby readyPeers asymmetry (initiator now included)
- Add extractEpisodeId() parsing S01E01, Season 1 Ep 1, Folge 5, Ep. 3, #42
- Add isDifferentEpisode() guard blocking cross-episode sync commands
- Add sameEpisode() for format-tolerant lobby title matching
- Guard controlled via autoSyncNextEpisode setting
- Reduce reactive lock 1000ms → 300ms
- Fix routeToContent unbounded retry (max 3 attempts)
- Fix server bcrypt.hash failure crashing join flow
- Fix sameEpisode(null, title) returning true
- Fix forceSyncTimeout leak on rapid force sync clicks
- Persist lastSeqBySender across service worker restarts
- Bump version: 1.8.10 → 1.9.0
2026-05-28 04:54:54 +02:00
Koala 1fba2fb69c perf: add command sequence numbers, episode-aware sync guard, fix echo suppression
- Add monotonically increasing seq per peer (localSeq + lastSeqBySender)
- Server relays seq field for stale command detection (backward compatible)
- Replace expectedEvents (1500ms timeout) with _suppressTimers (per-type, 300ms)
- Fix FORCE_SYNC_ACK missing seq (stale-ACK could trigger premature exec)
- Fix episode lobby readyPeers asymmetry (initiator never broadcasted readiness)
- Add extractEpisodeId() parsing S01E01/Season 1 Episode 1/Folge 5 patterns
- Add isDifferentEpisode() guard blocking sync commands across different episodes
- add sameEpisode() for format-tolerant lobby title matching
- Guard controlled by autoSyncNextEpisode setting with disable hint in warning
- Reduce reactive lock from 1000ms to 300ms (seq numbers handle ordering)
2026-05-28 04:30:32 +02:00
Koala db11812bd6 fix: suppress seek events when solo, add moz-extension CORS, add server logging 2026-05-26 01:06:04 +02:00
Koala da6a1cc643 Server updates 2026-05-25 22:59:54 +02:00
Koala 8b3f9e1242 fix: sync FORCE_SYNC_TIMEOUT constant with actual usage, tokenize showError cleanup, fix EVENT_ACK indent
- FORCE_SYNC_TIMEOUT in shared/constants.js was 5000 but all code uses
  8500ms. Update constant to 8500 and reference it in background.js
  instead of hardcoded values (4 occurrences)
- Add errorToken counter in popup showError() so stale 5s timeout
  doesn't clear styling of a newer error that arrived in between
- Fix EVENT_ACK handler indentation in server/index.js to match
  surrounding socket.on handlers
2026-05-25 12:28:22 +02:00
Koala c621685aae fix: use real client IP for auth rate limiting; preserve lobby across socket disconnect
- Store real client IP from x-forwarded-for on socket for use in JOIN_ROOM
  auth rate limiting (was using proxy IP, breaking brute-force protection)
- Remove clearEpisodeLobbyState() from socket.onclose to preserve lobby
  across brief disconnects; ensureState() recovers lobby + timeout on reconnect
- Lobby is still properly cleared on intentional LEAVE_ROOM and room switch
2026-05-25 12:24:14 +02:00
Koala b98cfc9ca1 fix(server): add missing room creation lock to prevent concurrent join race
Two parallel JOIN_ROOM to a non-existent room could race past each
other during bcrypt.hash, causing the second to overwrite the first
room (password hash lost). The lock was read but never written.

- Create lock promise before bcrypt.hash async boundary
- Release in finally to cover all exit paths (success, MAX_ROOMS, error)
- Concurrent waiters now correctly await existing room creation
2026-05-25 12:23:16 +02:00
Koala 284b82a910 fix: v1.7.0 - critical bug fixes, race conditions, memory leaks, null guards, server hardening 2026-05-25 11:48:54 +02:00
Koala 4909a86a13 feat: implement sprint 1 quick wins - toast system, notifications, UX polish
- Add central toast notification system (popup.html, popup.js)
- Add browser notifications toggle (opt-in) with event toasts
- Fix interpolation memory leak (unload listener)
- Add /health endpoint with IP-based rate limiting (server)
- Improve tab sorting (current tab first, matches, alphabetical)
- Add copy-to-clipboard visual feedback with toast
- Show targetTime in last action card for seek/force sync
- Add explicit video cleanup when element removed (content.js)
- Update ROADMAP.md to remove implemented features
2026-05-25 09:53:25 +02:00
Koala 92bec29215 Remove exaggerated marketing claims and fix technical inaccuracies across docs and website 2026-05-25 09:37:13 +02:00
Timo 385602c194 fix(extension,server): resolve 12 audit findings (lobby cleanup, dedup race, protocol version, rate limiting, observer scope, and more) 2026-05-25 02:14:59 +02:00
MacBook 01ce3ec99e chore: migrate domains from shik3i.net to koalastuff.net 2026-05-16 13:08:53 +02:00
MacBook 5157428e74 docs: comprehensive repository polish + step-by-step user guide
Documentation Rewrites:
- AI_INIT.md: fix duplicate section numbers, add file responsibility map,
  fix stale manual mirror instruction, add room ID constraint
- PRIVACY.md: add TL;DR statement, data retention table, explicit
  <all_urls> justification, self-hosted instance disclaimer
- CONTRIBUTING.md: add local testing guide, version warning, room ID
  constraint, bug report requirements
- shared/README.md: complete event table (all 15 events), fix stale
  manifest.json reference
- docs/SYNC_GUIDE.md: Chrome→Browser, add README.md to sync list,
  drop stale RC5 reference
- server/README.md: sync env defaults with .env.example (1000/50)

New Documentation:
- docs/HOW_IT_WORKS.md: 10-step walkthrough covering room creation,
  invitation bridge flow, synchronized playback, force sync protocol,
  heartbeat system, and episode auto-sync. Includes exact data payloads.

Infrastructure Cleanup:
- docker-compose.yml: remove deprecated version key
- .dockerignore: remove dead .bat/.sh patterns
- README.md: add self-hosting extension config tip, link HOW_IT_WORKS
2026-05-04 05:41:43 +02:00
MacBook f7829bbebb security: harden server relay + documentation audit
Server Security (S-1 through S-8):
- S-1: Type-check and clamp peerId, protocolVersion, password
- S-2: Validate numeric/boolean/enum fields in relay peerData
- S-3: Construct explicit relay payload (stop spreading raw data)
- S-4: Type-check targetId and actionTimestamp in EVENT_ACK
- S-5: Restrict room IDs to [a-zA-Z0-9-] only
- S-7: Add eventCounts periodic cleanup alongside connectionCounts
- S-8: Guard version parsing against NaN bypass

Documentation (P-1, R-1 through R-6):
- P-1: Fix PRIVACY.md typo, document all in-memory data maps
- R-1/R-5: Fix stale sync-constants.bat references in shared/
- R-2: Fix stale lastTargetState ref in ARCHITECTURE.md
- R-3: Extension README title reflects cross-browser support
- R-6: Document content injection markers in scripts/README.md
2026-05-04 05:19:18 +02:00
MacBook 6093da4dc6 feat: add docker-compose example and update GHCR deployment links 2026-05-04 05:12:22 +02:00
MacBook 5440d136fe docs: complete audit of all READMEs for consistency 2026-05-04 04:46:27 +02:00
MacBook c9cf7c49dc Fix logic flaws and update documentation 2026-05-01 06:06:46 +02:00
Timo 55c2d4ed0d feat: Auto-Sync Next Episode v1.2.0
Adds a new toggleable feature that detects episode transitions via
mediaTitle mutation (loadeddata/MutationObserver), pauses the video,
and waits for all room peers to load the same episode before
executing a coordinated Force Sync play at 0:00.

Protocol:
- Add EPISODE_LOBBY and EPISODE_READY events to shared/constants.js
- Add EPISODE_LOBBY_TIMEOUT (60s) constant
- Relay both new events in server/index.js

Content Script (content.js):
- Layered detection: loadeddata + MutationObserver src-change + heartbeat
- Debounced onEpisodeTransition() sends signal ONLY; no eager pause
- PAUSE_FOR_LOBBY handler pauses only after background confirms feature enabled
- startLobbyPoll() polls title match without premature pause for non-initiators
- checkAndReportLobbyReady() pauses and sends EPISODE_READY_LOCAL on match
- CONTENT_BOOT recovery for re-injection after hard navigation

Background (background.js):
- Episode lobby state persisted in chrome.storage.session with recovery
- EPISODE_CHANGED: checks setting, creates lobby, sends PAUSE_FOR_LOBBY to tab
- EPISODE_LOBBY/READY server event handlers with dedup logic
- 60s timeout cancels lobby (Option B) with Chrome failure notification
- Peer departure handled: removes from readyPeers, re-checks completion
- executeEpisodeLobby() reuses existing Force Sync pipeline at targetTime 0.0
- Lobby cleared on LEAVE_ROOM; status exposed in GET_STATUS

Popup:
- Auto-Sync Next Episode toggle in Settings tab (default: off, opt-in)
- Episode Lobby status card in Sync tab with peer readiness display
- LOBBY_UPDATE message handler for real-time UI updates

Bumps APP_VERSION and manifest to 1.2.0
2026-04-25 16:43:10 +02:00