Compare commits

...

1 Commits

Author SHA1 Message Date
MacBook c9cf7c49dc Fix logic flaws and update documentation 2026-05-01 06:06:46 +02:00
6 changed files with 44 additions and 30 deletions
+3 -2
View File
@@ -23,7 +23,7 @@ KoalaSync is a specialized tool for **synchronized video playback** across multi
- `docker-compose.yml`: Root-level orchestration for the relay server.
> [!IMPORTANT]
> **Single Source of Truth**: `shared/constants.js` and `shared/blacklist.js` are the master files. They must be synchronized to the `extension/shared/` directory using `.\scripts\sync-constants.bat` or `./scripts/sync-constants.sh`.
> **Single Source of Truth**: `shared/constants.js` and `shared/blacklist.js` are the master files. They must be synchronized to the `extension/shared/` directory using `node scripts/build-extension.js`.
> - **Extension Modules** (`background.js`, `popup.js`) import directly from `./shared/constants.js`.
> - **Content Scripts** (`content.js`) use a **manual synchronous mirror** to prevent race conditions during page load. Always verify parity after sync.
@@ -56,6 +56,7 @@ The popup UI follows a strict design system. Do not modify these variables or th
## 5. Non-Negotiables (Core Logic)
The following features are critical and must not be removed or fundamentally altered:
- **Two-Phase Force Sync**: The `Prepare``ACK``Execute` flow ensures all peers are buffered before playback resumes.
- **Episode Auto-Sync**: Ensures series binges stay perfectly synced. A lobby initiates during title transitions, freezing peers until everyone is ready.
- **Dual Heartbeat**:
- **Background Heartbeat (30s)**: Ensures session persistence even without a video element.
- **Content Heartbeat (15s)**: Transmits current video metadata (time, title).
@@ -84,7 +85,7 @@ The following features are critical and must not be removed or fundamentally alt
### Adding a Protocol Event
1. Add the event name to `shared/constants.js`.
2. Run the sync script (`.\scripts\sync-constants.bat` or `./scripts/sync-constants.sh`).
2. Run the build script (`node scripts/build-extension.js`).
3. Implement the handler in `server/index.js` and `background.js`.
### Testing Locally
+13 -5
View File
@@ -25,25 +25,33 @@ When a user interacts with a video:
Ensures all peers are frame-perfect and buffered before resuming:
1. **Prepare**: Initiator sends `FORCE_SYNC_PREPARE` with the target timestamp.
2. **Buffer**: Peers seek and pause. Once buffered (`readyState >= 3`), they send a `FORCE_SYNC_ACK`.
3. **Execute**: Once the Initiator collects ACKs (or after a 5s timeout), they send `FORCE_SYNC_EXECUTE`.
3. **Execute**: Once the Initiator collects ACKs (or after an 8.5s timeout), they send `FORCE_SYNC_EXECUTE`.
4. **Resume**: All peers call `play()` simultaneously.
## 4. Peer Lifecycle & Dual Heartbeat
## 4. Episode Auto-Sync
Maintains continuous synchronized viewing when watching series:
1. **Detection**: `content.js` monitors the Media Session API for title changes.
2. **Lobby Creation**: When a new title is detected, the peer initiates an `EPISODE_LOBBY` and broadcasts the new title.
3. **Wait State**: All peers freeze their video until they have also loaded the exact same title.
4. **Mid-Lobby Joins**: If a new user joins the room during an active lobby, the lobby initiator broadcasts the active lobby state so the newcomer can sync up.
5. **Resume**: Once all peers report `EPISODE_READY`, the lobby is resolved and playback resumes perfectly.
## 5. Peer Lifecycle & Dual Heartbeat
To maintain a clean room state and eliminate "Ghost Peers":
- **Session Heartbeat (Background)**: Every 30 seconds, `background.js` sends an "I'm alive" signal to the server. This keeps you in the room even if no video is playing.
- **Video Heartbeat (Content)**: Every 15 seconds, `content.js` sends current playback metadata (time, title, state) if a video is found.
- **Server Pruning**: The server runs a "Reaper" every 2 minutes. If a peer has sent **zero** activity (no events and no heartbeats) for 5 minutes, they are forcefully disconnected.
- **Immediate Cleanup**: Rooms are deleted instantly when the last peer leaves or disconnects.
## 5. Security & Stability
## 6. Security & Stability
- **Service Worker Lifecycle**: Uses `chrome.alarms` to prevent the Manifest V3 service worker from suspending while in an active room.
- **Rate Limiting**: Server-side per-socket and per-IP rate limits to prevent sync-spamming or DoS.
- **Noise Filtering**: Uses a curated blacklist of domains (Search Engines, Social Media) to declutter the "Target Tab" selector in the popup.
- **Diagnostics**: A "Dev" tab provides real-time access to the underlying `<video>` state (`readyState`, `paused`, `currentTime`) for easier troubleshooting.
## 6. Constant Synchronization & Consistency
## 7. Constant Synchronization & Consistency
To maintain a "Single Source of Truth" across the server and extension without using a bundler:
- **Relay Server & Extension Modules**: `background.js` and `popup.js` import constants directly from `shared/constants.js`.
- **Content Scripts**: To ensure zero-latency execution, `content.js` uses a manual mirror of `EVENTS`.
- **Synchronization**: The `./scripts/sync-constants.sh` script ensures that the `shared/` folder within the `extension/` directory is kept up-to-date with the root `shared/` source.
- **Synchronization**: The `node scripts/build-extension.js` script ensures that the `shared/` folder within the `extension/` directory is kept up-to-date with the root `shared/` source while packaging artifacts.
- **Verification**: Any protocol change requires a manual verification sweep across all three constant locations (Shared, Server, and Content Script Mirror).
+2 -2
View File
@@ -41,9 +41,9 @@ docker-compose up -d --build
The server will be available at `ws://localhost:3000`.
### 2. Chrome Extension
1. **Synchronize Protocol**: From the root directory, run the sync script to copy the master constants to the extension folder:
1. **Synchronize Protocol**: From the root directory, run the build script to copy the master constants and prepare the extension:
```bash
./scripts/sync-constants.sh
node scripts/build-extension.js
```
2. Open Chrome and go to `chrome://extensions/`.
3. Enable **Developer mode** (top right).
+10 -17
View File
@@ -5,8 +5,8 @@ KoalaSync uses a "Single Source of Truth" for its communication protocol constan
To ensure that the extension and the relay server are always using the exact same event names and protocol versions, we maintain a mirrored copy of the shared files within the `extension/shared/` folder.
## When should you run the sync script?
You MUST run the synchronization script in any of the following scenarios:
## When should you run the build script?
You MUST run the build script in any of the following scenarios:
1. **After a fresh `git clone` or `git pull`** (as the synced files are ignored by git).
2. **After modifying** `shared/constants.js`.
3. **After modifying** `shared/blacklist.js`.
@@ -15,29 +15,22 @@ You MUST run the synchronization script in any of the following scenarios:
## How to sync
### On Windows
Run the batch script from the repository root:
```powershell
.\scripts\sync-constants.bat
```
### On macOS / Linux
Run the shell script from the repository root:
Run the Node.js build script from the repository root:
```bash
./scripts/sync-constants.sh
node scripts/build-extension.js
```
## What does it do?
The script performs the following actions:
1. Ensures the `extension/shared/` directory exists.
2. Copies `shared/constants.js` to `extension/shared/constants.js`.
3. Copies `shared/blacklist.js` to `extension/shared/blacklist.js`.
The build script performs the following actions:
1. Synchronizes protocol constants by copying `shared/constants.js` and `shared/blacklist.js` into `extension/shared/`.
2. Compiles browser-specific manifest files.
3. Packages the final ready-to-publish extension artifacts for Chrome and Firefox into the `dist/` directory.
## Protocol Versioning
As of v1.0.0-RC5, the system enforces a strict `protocolVersion` check during the `JOIN_ROOM` handshake.
- The version is defined in `shared/constants.js`.
- If the extension and server versions mismatch, the server will reject the connection with an `Incompatible protocol version` error.
- **Always run the sync script** after bumping the version number to ensure both components are updated.
- **Always run the build script** after bumping the version number to ensure both components are updated.
> [!CAUTION]
> **NEVER** edit the files inside `extension/shared/` directly. They will be overwritten the next time the sync script is run. Always edit the files in the root `shared/` directory and then run the sync script.
> **NEVER** edit the files inside `extension/shared/` directly. They will be overwritten the next time the build script is run. Always edit the files in the root `shared/` directory and then run the build script.
+15 -4
View File
@@ -542,6 +542,10 @@ function handleServerEvent(event, data) {
currentRoom.peers.push(createPeerData(data));
if (storageInitialized) chrome.storage.session.set({ currentRoom });
chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: currentRoom.peers }).catch(() => {});
if (episodeLobby && episodeLobby.initiatorPeerId === peerId) {
emit(EVENTS.EPISODE_LOBBY, { peerId, expectedTitle: episodeLobby.expectedTitle });
}
}
} else if (data.status === 'left') {
currentRoom.peers = currentRoom.peers.filter(p => (p.peerId || p) !== data.peerId);
@@ -552,6 +556,13 @@ function handleServerEvent(event, data) {
if (episodeLobby) {
checkEpisodeLobbyPeerDeparture();
}
if (isForceSyncInitiator) {
const peerCount = currentRoom.peers ? currentRoom.peers.length : 1;
if (forceSyncAcks.size >= peerCount) {
executeForceSync();
}
}
} else {
// Heartbeat/Update: Update tabTitle for matching
const peer = currentRoom.peers.find(p => (p.peerId || p) === data.peerId);
@@ -691,7 +702,7 @@ function executeEpisodeLobby() {
// Trigger a standard Force Sync at targetTime 0.0
isForceSyncInitiator = true;
forceSyncAcks.clear();
const deadline = Date.now() + 5000;
const deadline = Date.now() + 8500;
chrome.storage.session.set({
isForceSyncInitiator: true,
forceSyncAcks: [],
@@ -707,7 +718,7 @@ function executeEpisodeLobby() {
addLog('Force Sync (Episode): Timeout waiting for ACKs, executing anyway...', 'warn');
executeForceSync();
}
}, 5000);
}, 8500);
}
function checkEpisodeLobbyCompletion() {
@@ -943,7 +954,7 @@ async function handleAsyncMessage(message, sender, sendResponse) {
if (message.action === EVENTS.FORCE_SYNC_PREPARE) {
isForceSyncInitiator = true;
forceSyncAcks.clear();
const deadline = Date.now() + 5000;
const deadline = Date.now() + 8500;
chrome.storage.session.set({
isForceSyncInitiator: true,
forceSyncAcks: [],
@@ -958,7 +969,7 @@ async function handleAsyncMessage(message, sender, sendResponse) {
addLog('Force Sync: Timeout waiting for ACKs, executing anyway...', 'warn');
executeForceSync();
}
}, 5000);
}, 8500);
}
addToHistory(message.action, 'You');
emit(message.action, { ...message.payload, peerId });
+1
View File
@@ -370,6 +370,7 @@ io.on('connection', (socket) => {
});
socket.on(EVENTS.EVENT_ACK, (data) => {
if (!data || typeof data !== 'object') return;
if (!data.targetId) return;
const senderMapping = socketToRoom.get(socket.id);