mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-08-16 14:28:20 +00:00
chore: release v1.6.0 and apply fixes
This commit is contained in:
@@ -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
@@ -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,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
@@ -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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "koalasync",
|
||||
"version": "1.5.4",
|
||||
"version": "1.6.0",
|
||||
"description": "KoalaSync Build Scripts",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user