- CI release workflow now extracts version from tag (v1.4.0 → 1.4.0)
and injects it into manifest.base.json, shared/constants.js, and
package.json before building — the tag is the single source of truth
- Build script now copies README.md alongside constants.js and
blacklist.js from /shared → /extension/shared/ (full mirror)
- .gitignore updated: extension/shared/ is fully generated by build
- AI_INIT.md: simplified release workflow, fixed Chrome-only reference
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
- Add language toggle and German translation to website
- Optimize manifest description
- Add robots.txt
- Add open source transparency clause to privacy policy
Streaming players (Emby, Jellyfin, etc.) perform frequent internal seeks
for buffering that are < 1s in magnitude. These were being relayed to
peers, causing brief video freezes every few minutes.
Fix:
- MIN_SEEK_DELTA = 3.0s: ignore seeks smaller than 3 seconds
- 800ms debounce: settle rapid-fire seeks (e.g. scrubbing) before relaying
- Programmatic seek suppression still takes priority via expectedEvents
- lastReportedSeekTime baseline updated on programmatic seeks too
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