mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-27 12:29:35 +00:00
Security & Stability: Fixed SW race, ping-pong loops, and invite parsing
This commit is contained in:
+17
-10
@@ -14,6 +14,7 @@ let history = []; // New: for Action History
|
||||
let storageInitialized = false;
|
||||
let pendingLogs = [];
|
||||
let pendingHistory = [];
|
||||
let eventQueue = [];
|
||||
|
||||
// Restore state from session storage
|
||||
chrome.storage.session.get(['logs', 'history'], (data) => {
|
||||
@@ -182,6 +183,11 @@ async function connect() {
|
||||
protocolVersion: PROTOCOL_VERSION
|
||||
});
|
||||
}
|
||||
while (eventQueue.length > 0) {
|
||||
const queuedMsg = eventQueue.shift();
|
||||
emit(queuedMsg.event, queuedMsg.data);
|
||||
}
|
||||
eventQueue = []; // Explicitly reset to avoid memory leaks
|
||||
} else if (msg.startsWith('42')) {
|
||||
// Event: 42["event", data]
|
||||
try {
|
||||
@@ -272,6 +278,8 @@ function emit(event, data) {
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
const msg = `42${JSON.stringify([event, data])}`;
|
||||
socket.send(msg);
|
||||
} else {
|
||||
eventQueue.push({ event, data });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,15 +433,14 @@ async function routeToContent(action, payload) {
|
||||
}
|
||||
|
||||
// --- Keep-Alive Mechanism ---
|
||||
chrome.alarms.create('keepAlive', { periodInMinutes: 1 }); // every 1m
|
||||
chrome.alarms.onAlarm.addListener((alarm) => {
|
||||
if (alarm.name === 'keepAlive') {
|
||||
// console.log('SW KeepAlive Heartbeat');
|
||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||
connect();
|
||||
}
|
||||
chrome.alarms.clearAll();
|
||||
setInterval(() => {
|
||||
// Calling a chrome API keeps the SW alive in MV3 (Chrome 110+)
|
||||
chrome.storage.session.get('keepAlive', () => {});
|
||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||
connect();
|
||||
}
|
||||
});
|
||||
}, 20000); // every 20s
|
||||
|
||||
// --- Extension Message Listeners ---
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
@@ -459,9 +466,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
logs = [];
|
||||
sendResponse({ status: 'ok' });
|
||||
} else if (message.type === 'GET_LOGS') {
|
||||
sendResponse(logs);
|
||||
sendResponse(storageInitialized ? logs : pendingLogs);
|
||||
} else if (message.type === 'GET_HISTORY') {
|
||||
sendResponse(history);
|
||||
sendResponse(storageInitialized ? history : pendingHistory);
|
||||
} else if (message.type === 'GET_ROOM_LIST') {
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(`42${JSON.stringify([EVENTS.GET_ROOMS])}`);
|
||||
|
||||
+21
-11
@@ -7,7 +7,7 @@
|
||||
if (window.koalaSyncInjected) return;
|
||||
window.koalaSyncInjected = true;
|
||||
|
||||
let processingCommandUntil = 0;
|
||||
let lastTargetState = null;
|
||||
|
||||
// --- Helper: find the best video element on the page ---
|
||||
function findVideo() {
|
||||
@@ -20,7 +20,6 @@
|
||||
const video = findVideo();
|
||||
if (!video) return;
|
||||
|
||||
processingCommandUntil = Date.now() + 1000;
|
||||
try {
|
||||
const host = window.location.hostname.toLowerCase();
|
||||
const isYouTube = host.includes('youtube.com');
|
||||
@@ -31,11 +30,12 @@
|
||||
if (ytButton) {
|
||||
const isCurrentlyPlaying = !video.paused;
|
||||
if ((action === 'play' && !isCurrentlyPlaying) || (action === 'pause' && isCurrentlyPlaying)) {
|
||||
lastTargetState = action === 'play' ? 'playing' : 'paused';
|
||||
ytButton.click();
|
||||
}
|
||||
if (action === 'seek') video.currentTime = data.targetTime;
|
||||
return;
|
||||
}
|
||||
if (action === 'seek') video.currentTime = data.targetTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTwitch) {
|
||||
@@ -43,17 +43,20 @@
|
||||
if (twitchButton) {
|
||||
const isCurrentlyPlaying = !video.paused;
|
||||
if ((action === 'play' && !isCurrentlyPlaying) || (action === 'pause' && isCurrentlyPlaying)) {
|
||||
lastTargetState = action === 'play' ? 'playing' : 'paused';
|
||||
twitchButton.click();
|
||||
}
|
||||
if (action === 'seek') video.currentTime = data.targetTime;
|
||||
return;
|
||||
}
|
||||
if (action === 'seek') video.currentTime = data.targetTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback for native HTML5
|
||||
if (action === 'play') {
|
||||
lastTargetState = 'playing';
|
||||
video.play().catch(() => {});
|
||||
} else if (action === 'pause') {
|
||||
lastTargetState = 'paused';
|
||||
video.pause();
|
||||
} else if (action === 'seek') {
|
||||
video.currentTime = data.targetTime;
|
||||
@@ -104,17 +107,15 @@
|
||||
} else if (action === 'seek') {
|
||||
tryMediaAction('seek', payload);
|
||||
} else if (action === 'force_sync_prepare') {
|
||||
processingCommandUntil = Date.now() + 10000;
|
||||
if (!payload || payload.targetTime === undefined) return;
|
||||
const video = findVideo();
|
||||
if (video) {
|
||||
lastTargetState = 'paused';
|
||||
video.pause();
|
||||
video.currentTime = payload.targetTime;
|
||||
pollSeekReady(payload.targetTime).then(() => {
|
||||
chrome.runtime.sendMessage({ type: 'FORCE_SYNC_ACK' });
|
||||
processingCommandUntil = Date.now() + 1000;
|
||||
});
|
||||
} else {
|
||||
processingCommandUntil = 0;
|
||||
}
|
||||
} else if (action === 'force_sync_execute') {
|
||||
tryMediaAction('play');
|
||||
@@ -124,10 +125,19 @@
|
||||
|
||||
// Detect native events
|
||||
function reportEvent(action) {
|
||||
if (Date.now() < processingCommandUntil) return;
|
||||
const video = findVideo();
|
||||
if (!video) return;
|
||||
|
||||
const eventState = action === 'play' ? 'playing' : (action === 'pause' ? 'paused' : null);
|
||||
|
||||
if (eventState && lastTargetState === eventState) {
|
||||
lastTargetState = null; // Consume the match
|
||||
return; // Ignore event caused by our programmatic action
|
||||
}
|
||||
if (action !== 'seek') {
|
||||
lastTargetState = null; // Reset on mismatch
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
type: 'CONTENT_EVENT',
|
||||
action,
|
||||
|
||||
+9
-7
@@ -260,23 +260,25 @@ function checkInviteLink() {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
const tab = tabs[0];
|
||||
if (tab && tab.url && tab.url.includes(OFFICIAL_LANDING_PAGE_URL) && tab.url.includes('#join:')) {
|
||||
const parts = tab.url.split('#join:')[1].split(':');
|
||||
const rawHash = tab.url.split('#join:')[1];
|
||||
const parts = rawHash.split(':');
|
||||
if (parts.length >= 2) {
|
||||
const roomId = parts[0];
|
||||
const password = parts[1];
|
||||
const roomId = parts.shift();
|
||||
let useCustomServer = false;
|
||||
let serverUrl = '';
|
||||
|
||||
// Smart Link: Parse Server Config if present
|
||||
if (parts.length >= 4) {
|
||||
useCustomServer = parts[2] === '1';
|
||||
serverUrl = decodeURIComponent(parts[3]);
|
||||
if (parts.length >= 3 && (parts[parts.length - 2] === '0' || parts[parts.length - 2] === '1')) {
|
||||
serverUrl = decodeURIComponent(parts.pop());
|
||||
useCustomServer = parts.pop() === '1';
|
||||
}
|
||||
|
||||
const password = parts.join(':');
|
||||
|
||||
elements.roomId.value = roomId;
|
||||
elements.password.value = password;
|
||||
|
||||
if (parts.length >= 4) {
|
||||
if (serverUrl || useCustomServer) {
|
||||
elements.serverUrl.value = serverUrl;
|
||||
setServerMode(useCustomServer);
|
||||
chrome.storage.sync.set({ serverUrl, useCustomServer });
|
||||
|
||||
Reference in New Issue
Block a user