chore: release v1.6.0 and apply fixes

This commit is contained in:
Koala
2026-05-23 00:19:02 +02:00
parent d3f680e313
commit 0a555942f8
5 changed files with 53 additions and 38 deletions
+3 -3
View File
@@ -10,7 +10,7 @@ This guide walks through the complete user flow of KoalaSync, from creating a ro
2. The extension adds a small icon to your browser toolbar.
3. On first install, a unique 8-character **Peer ID** is generated locally and stored in `chrome.storage.local`. This ID is never sent to any external service — it only travels to the relay server when you join a room.
> **What's stored locally**: `peerId` (8-char hex), `username` (customizable), `serverUrl`, `filterNoise` preference. All stored via `chrome.storage.sync` and `chrome.storage.local`.
> **What's stored locally**: `peerId` (8-char hex), `username` (customizable, defaults to a readable adjective-noun pair), `serverUrl`, `filterNoise` preference. All stored via `chrome.storage.sync` and `chrome.storage.local`.
---
@@ -109,7 +109,7 @@ Both users now need to select which browser tab contains the video to sync:
4. Tabs with a **matching video title** are highlighted with a ⭐ prefix for easy identification.
5. Selecting a tab causes `background.js` to set `currentTabId` and inject `content.js` into that tab via `chrome.scripting.executeScript`.
> **What `content.js` does on injection**: Finds the first `<video>` element on the page and attaches event listeners for `play`, `pause`, `seeked`, `timeupdate`, and `volumechange`. It uses an `expectedEvents` Set to distinguish between user actions and programmatic actions (loop prevention).
> **What `content.js` does on injection**: Finds the first `<video>` element on the page and attaches event listeners for `play`, `pause`, `seeked`, and `loadeddata`. (Time and volume state are tracked via a 15-second heartbeat interval, not continuous event listeners). It uses an `expectedEvents` Set to distinguish between user actions and programmatic actions (loop prevention).
---
@@ -155,7 +155,7 @@ While in a room, two heartbeats keep the session alive:
| Heartbeat | Interval | Source | Purpose |
|:----------|:---------|:-------|:--------|
| **Background** | 30 seconds | `background.js` | Signals "I'm still connected" even without a video |
| **Background** | 1 minute | `background.js` | Signals "I'm still connected" and handles 5-min auto-reconnect fallback |
| **Content** | 15 seconds | `content.js` | Sends video metadata: `currentTime`, `mediaTitle`, `playbackState`, `volume`, `muted` |
- **Server Reaper**: Every 2 minutes, the server checks for peers with no activity for 5+ minutes and disconnects them ("dead peer pruning").
+35 -30
View File
@@ -107,6 +107,7 @@ let reconnectStartTime = null; // New: track when reconnection started
let reconnectFailed = false; // New: true if we hit the 5-min cap
let slowReconnectTimer = null; // Infinite slow background reconnect timer
let isSlowReconnectAttempt = false; // True during slow background reconnect execution
let lastSlowReconnectAttempt = 0;
// Force Sync Coordination
let isForceSyncInitiator = false;
@@ -172,12 +173,19 @@ async function getPeerId() {
async function getSettings() {
return new Promise(resolve => {
chrome.storage.sync.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'username'], (data) => {
let username = data.username;
if (!username) {
const adjs = ['Happy', 'Cool', 'Fast', 'Smart', 'Brave', 'Calm', 'Sneaky', 'Lazy', 'Wild', 'Chill', 'Lucky', 'Epic'];
const nouns = ['Koala', 'Panda', 'Tiger', 'Eagle', 'Fox', 'Bear', 'Wolf', 'Lion', 'Hawk', 'Seal', 'Owl', 'Shark'];
username = `${adjs[Math.floor(Math.random() * adjs.length)]}${nouns[Math.floor(Math.random() * nouns.length)]}`;
chrome.storage.sync.set({ username });
}
resolve({
serverUrl: data.serverUrl || '',
useCustomServer: data.useCustomServer || false,
roomId: data.roomId || '',
password: data.password || '',
username: data.username || ''
username: username
});
});
});
@@ -240,8 +248,7 @@ async function connect() {
if (reconnectFailed && !isCurrentSlowRetry) {
isConnecting = false;
scheduleSlowReconnect(); // Keep checking in the background
return;
return; // Let keepAlive alarm handle the 5-min retry interval
}
broadcastConnectionStatus('connecting');
@@ -429,7 +436,6 @@ function scheduleReconnect() {
isSlowReconnectAttempt = false;
if (reconnectFailed) {
scheduleSlowReconnect();
return;
}
@@ -441,7 +447,6 @@ function scheduleReconnect() {
chrome.storage.session.set({ reconnectFailed: true });
addLog('Reconnection failed after 5 minutes. Entering slow background retry mode.', 'error');
broadcastConnectionStatus('reconnect_failed');
scheduleSlowReconnect();
return;
}
@@ -452,19 +457,7 @@ function scheduleReconnect() {
}, reconnectDelay);
}
function scheduleSlowReconnect() {
if (slowReconnectTimer || socket || isConnecting) return;
slowReconnectTimer = setTimeout(async () => {
slowReconnectTimer = null;
await ensureState();
if (reconnectFailed && !socket && !isConnecting) {
addLog('Performing background reconnection attempt...', 'info');
isSlowReconnectAttempt = true;
connect();
}
}, 300000); // 5 minutes (300,000ms)
}
// Slow reconnect logic is now handled in the keepAlive alarm
function emit(event, data) {
if (socket && socket.readyState === WebSocket.OPEN && isNamespaceJoined) {
@@ -589,17 +582,16 @@ function handleServerEvent(event, data) {
addToHistory(event, data.senderId);
showNotification(data.senderId, event);
// Force Sync Execute Remote Reactive Update
updateLocalPeerState(data.senderId, {
playbackState: 'playing'
});
// (The sender's state is updated below with everyone else)
}
// Reset reactive update locks for all peers so the next playing heartbeat is accepted immediately
// Force Sync Execute Remote Reactive Update:
// Set all peers to playing and apply a reactive lock to block stale heartbeats
if (currentRoom && Array.isArray(currentRoom.peers)) {
currentRoom.peers.forEach(peer => {
if (peer && typeof peer === 'object') {
peer.lastReactiveUpdate = 0;
peer.playbackState = 'playing';
peer.lastReactiveUpdate = Date.now();
}
});
if (storageInitialized) chrome.storage.session.set({ currentRoom });
@@ -749,19 +741,23 @@ function executeForceSync() {
forceSyncDeadline: null
});
// Reset reactive update locks for all peers so the next playing heartbeat is accepted immediately
// Set all peers to playing and apply a reactive lock to block stale heartbeats
if (currentRoom && Array.isArray(currentRoom.peers)) {
currentRoom.peers.forEach(peer => {
if (peer && typeof peer === 'object') {
peer.lastReactiveUpdate = 0;
peer.playbackState = 'playing';
peer.lastReactiveUpdate = Date.now();
}
});
if (storageInitialized) chrome.storage.session.set({ currentRoom });
chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: currentRoom.peers }).catch(() => {});
}
emit(EVENTS.FORCE_SYNC_EXECUTE, {});
routeToContent(EVENTS.FORCE_SYNC_EXECUTE, {});
const executionTimestamp = Date.now();
updateLastAction(EVENTS.FORCE_SYNC_EXECUTE, 'You', executionTimestamp);
emit(EVENTS.FORCE_SYNC_EXECUTE, { actionTimestamp: executionTimestamp });
routeToContent(EVENTS.FORCE_SYNC_EXECUTE, { actionTimestamp: executionTimestamp });
addLog('Force Sync Executed', 'success');
}
@@ -912,7 +908,16 @@ chrome.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === 'keepAlive') {
chrome.storage.session.get('keepAlive', () => {});
if (!socket || socket.readyState !== WebSocket.OPEN) {
connect();
if (reconnectFailed) {
if (Date.now() - lastSlowReconnectAttempt >= 300000) {
lastSlowReconnectAttempt = Date.now();
isSlowReconnectAttempt = true;
addLog('Alarm triggered 5-min slow reconnect attempt', 'info');
connect();
}
} else {
connect();
}
} else if (currentRoom) {
// Heartbeat Logic: Always include identity metadata
const settings = await getSettings();
@@ -1256,7 +1261,7 @@ async function handleAsyncMessage(message, sender, sendResponse) {
// Check setting
const epSettings = await chrome.storage.sync.get(['autoSyncNextEpisode']);
if (!epSettings.autoSyncNextEpisode) {
if (epSettings.autoSyncNextEpisode === false) {
addLog(`Episode change detected ("${newTitle}") but Auto-Sync is disabled.`, 'info');
sendResponse({ status: 'disabled' });
return;
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "KoalaSync",
"version": "1.5.4",
"version": "1.6.0",
"description": "Watch party extension to synchronize video playback on YouTube, Twitch, Netflix, and HTML5 sites in real-time with friends.",
"permissions": [
"storage",
+13 -3
View File
@@ -54,12 +54,20 @@ let lastPeersJson = null;
async function init() {
// Load Settings
const data = await chrome.storage.sync.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'filterNoise', 'username', 'autoSyncNextEpisode']);
let username = data.username;
if (!username) {
const adjs = ['Happy', 'Cool', 'Fast', 'Smart', 'Brave', 'Calm', 'Sneaky', 'Lazy', 'Wild', 'Chill', 'Lucky', 'Epic'];
const nouns = ['Koala', 'Panda', 'Tiger', 'Eagle', 'Fox', 'Bear', 'Wolf', 'Lion', 'Hawk', 'Seal', 'Owl', 'Shark'];
username = `${adjs[Math.floor(Math.random() * adjs.length)]}${nouns[Math.floor(Math.random() * nouns.length)]}`;
chrome.storage.sync.set({ username });
}
elements.serverUrl.value = data.serverUrl || '';
elements.roomId.value = data.roomId || '';
elements.password.value = data.password || '';
elements.username.value = data.username || '';
elements.username.value = username;
elements.filterNoise.checked = data.filterNoise !== false;
elements.autoSyncNextEpisode.checked = !!data.autoSyncNextEpisode;
elements.autoSyncNextEpisode.checked = data.autoSyncNextEpisode !== false;
// Set Version Info
const versionEl = document.getElementById('appVersion');
@@ -744,7 +752,9 @@ elements.createRoomBtn.addEventListener('click', () => {
const animals = ['koala', 'panda', 'tiger', 'eagle', 'fox', 'bear'];
const adj = ['happy', 'cool', 'fast', 'smart', 'brave', 'calm'];
const id = `${adj[Math.floor(Math.random() * adj.length)]}-${animals[Math.floor(Math.random() * animals.length)]}-${Math.floor(Math.random() * 100)}`;
const pass = Math.random().toString(36).substring(2, 8);
const array = new Uint32Array(1);
window.crypto.getRandomValues(array);
const pass = array[0].toString(36).substring(0, 6);
elements.roomId.value = id;
elements.password.value = pass;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "koalasync",
"version": "1.5.4",
"version": "1.6.0",
"description": "KoalaSync Build Scripts",
"private": true,
"scripts": {