mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-09-04 15:05:20 +00:00
Refactor: Comprehensive Security and Architecture Audit
This commit is contained in:
+29
-6
@@ -11,6 +11,13 @@ let currentTabId = null;
|
|||||||
let currentTabTitle = null; // New: for Smart Matching
|
let currentTabTitle = null; // New: for Smart Matching
|
||||||
let logs = [];
|
let logs = [];
|
||||||
let history = []; // New: for Action History
|
let history = []; // New: for Action History
|
||||||
|
|
||||||
|
// Restore state from session storage
|
||||||
|
chrome.storage.session.get(['logs', 'history'], (data) => {
|
||||||
|
if (data.logs) logs = data.logs;
|
||||||
|
if (data.history) history = data.history;
|
||||||
|
});
|
||||||
|
|
||||||
let reconnectTimer = null;
|
let reconnectTimer = null;
|
||||||
let reconnectStartTime = null; // New: track when reconnection started
|
let reconnectStartTime = null; // New: track when reconnection started
|
||||||
let reconnectFailed = false; // New: true if we hit the 5-min cap
|
let reconnectFailed = false; // New: true if we hit the 5-min cap
|
||||||
@@ -51,6 +58,7 @@ function addLog(message, type = 'info') {
|
|||||||
};
|
};
|
||||||
logs.unshift(log);
|
logs.unshift(log);
|
||||||
if (logs.length > 50) logs.pop();
|
if (logs.length > 50) logs.pop();
|
||||||
|
chrome.storage.session.set({ logs });
|
||||||
chrome.runtime.sendMessage({ type: 'LOG_UPDATE', log }).catch(() => {});
|
chrome.runtime.sendMessage({ type: 'LOG_UPDATE', log }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,8 +92,9 @@ async function connect() {
|
|||||||
// Strict WSS Enforcement
|
// Strict WSS Enforcement
|
||||||
const urlObj = new URL(finalUrl);
|
const urlObj = new URL(finalUrl);
|
||||||
const isLocal = urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1';
|
const isLocal = urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1';
|
||||||
if (!isLocal && urlObj.protocol === 'ws:') {
|
if (urlObj.protocol !== 'wss:' && !isLocal) {
|
||||||
finalUrl = finalUrl.replace('ws://', 'wss://');
|
urlObj.protocol = 'wss:';
|
||||||
|
finalUrl = urlObj.toString();
|
||||||
addLog('Security: Upgraded to wss:// for remote host.', 'warn');
|
addLog('Security: Upgraded to wss:// for remote host.', 'warn');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,6 +262,7 @@ function addToHistory(action, senderId) {
|
|||||||
};
|
};
|
||||||
history.unshift(historyEntry);
|
history.unshift(historyEntry);
|
||||||
if (history.length > 20) history.pop();
|
if (history.length > 20) history.pop();
|
||||||
|
chrome.storage.session.set({ history });
|
||||||
chrome.runtime.sendMessage({ type: 'HISTORY_UPDATE', history }).catch(() => {});
|
chrome.runtime.sendMessage({ type: 'HISTORY_UPDATE', history }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,9 +314,9 @@ function handleServerEvent(event, data) {
|
|||||||
if (isForceSyncInitiator) {
|
if (isForceSyncInitiator) {
|
||||||
forceSyncAcks.add(data.senderId);
|
forceSyncAcks.add(data.senderId);
|
||||||
addLog(`Received ACK from ${data.senderId} (${forceSyncAcks.size})`, 'info');
|
addLog(`Received ACK from ${data.senderId} (${forceSyncAcks.size})`, 'info');
|
||||||
// Check if all peers responded (minus ourselves)
|
// Check if all peers responded
|
||||||
const peerCount = currentRoom ? currentRoom.peers.length : 1;
|
const peerCount = currentRoom ? currentRoom.peers.length : 1;
|
||||||
if (forceSyncAcks.size >= peerCount - 1) {
|
if (forceSyncAcks.size >= peerCount) {
|
||||||
executeForceSync();
|
executeForceSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,7 +400,7 @@ async function routeToContent(action, payload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Keep-Alive Mechanism ---
|
// --- Keep-Alive Mechanism ---
|
||||||
chrome.alarms.create('keepAlive', { periodInMinutes: 0.25 }); // every 15s
|
chrome.alarms.create('keepAlive', { periodInMinutes: 1 }); // every 1m
|
||||||
chrome.alarms.onAlarm.addListener((alarm) => {
|
chrome.alarms.onAlarm.addListener((alarm) => {
|
||||||
if (alarm.name === 'keepAlive') {
|
if (alarm.name === 'keepAlive') {
|
||||||
// console.log('SW KeepAlive Heartbeat');
|
// console.log('SW KeepAlive Heartbeat');
|
||||||
@@ -448,6 +458,10 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|||||||
isForceSyncInitiator = true;
|
isForceSyncInitiator = true;
|
||||||
forceSyncAcks.clear();
|
forceSyncAcks.clear();
|
||||||
addLog('Initiating Force Sync...', 'info');
|
addLog('Initiating Force Sync...', 'info');
|
||||||
|
|
||||||
|
// Route to our own content script so we pause and seek
|
||||||
|
routeToContent(EVENTS.FORCE_SYNC_PREPARE, message.payload);
|
||||||
|
|
||||||
// Timeout if not everyone ACKs
|
// Timeout if not everyone ACKs
|
||||||
forceSyncTimeout = setTimeout(() => {
|
forceSyncTimeout = setTimeout(() => {
|
||||||
if (isForceSyncInitiator) {
|
if (isForceSyncInitiator) {
|
||||||
@@ -459,7 +473,16 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|||||||
addToHistory(message.action, 'You');
|
addToHistory(message.action, 'You');
|
||||||
emit(message.action, { ...message.payload, peerId });
|
emit(message.action, { ...message.payload, peerId });
|
||||||
} else if (message.type === 'FORCE_SYNC_ACK') {
|
} else if (message.type === 'FORCE_SYNC_ACK') {
|
||||||
emit(EVENTS.FORCE_SYNC_ACK, { peerId });
|
if (isForceSyncInitiator) {
|
||||||
|
forceSyncAcks.add(peerId);
|
||||||
|
addLog(`Local ACK received (${forceSyncAcks.size})`, 'info');
|
||||||
|
const peerCount = currentRoom ? currentRoom.peers.length : 1;
|
||||||
|
if (forceSyncAcks.size >= peerCount) {
|
||||||
|
executeForceSync();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit(EVENTS.FORCE_SYNC_ACK, { peerId });
|
||||||
|
}
|
||||||
} else if (message.type === 'HEARTBEAT') {
|
} else if (message.type === 'HEARTBEAT') {
|
||||||
if (sender.tab) {
|
if (sender.tab) {
|
||||||
currentTabId = sender.tab.id;
|
currentTabId = sender.tab.id;
|
||||||
|
|||||||
@@ -29,8 +29,7 @@
|
|||||||
if (isYouTube) {
|
if (isYouTube) {
|
||||||
const ytButton = document.querySelector('.ytp-play-button');
|
const ytButton = document.querySelector('.ytp-play-button');
|
||||||
if (ytButton) {
|
if (ytButton) {
|
||||||
const title = ytButton.getAttribute('aria-label') || '';
|
const isCurrentlyPlaying = !video.paused;
|
||||||
const isCurrentlyPlaying = title.toLowerCase().includes('pause');
|
|
||||||
if ((action === 'play' && !isCurrentlyPlaying) || (action === 'pause' && isCurrentlyPlaying)) {
|
if ((action === 'play' && !isCurrentlyPlaying) || (action === 'pause' && isCurrentlyPlaying)) {
|
||||||
ytButton.click();
|
ytButton.click();
|
||||||
}
|
}
|
||||||
@@ -42,9 +41,7 @@
|
|||||||
if (isTwitch) {
|
if (isTwitch) {
|
||||||
const twitchButton = document.querySelector('[data-a-target="player-play-pause-button"]');
|
const twitchButton = document.querySelector('[data-a-target="player-play-pause-button"]');
|
||||||
if (twitchButton) {
|
if (twitchButton) {
|
||||||
const label = twitchButton.getAttribute('aria-label')?.toLowerCase() || '';
|
const isCurrentlyPlaying = !video.paused;
|
||||||
// Check for common localized labels (pause, stoppen, arrête)
|
|
||||||
const isCurrentlyPlaying = label.includes('pause') || label.includes('stoppen') || label.includes('arrête');
|
|
||||||
if ((action === 'play' && !isCurrentlyPlaying) || (action === 'pause' && isCurrentlyPlaying)) {
|
if ((action === 'play' && !isCurrentlyPlaying) || (action === 'pause' && isCurrentlyPlaying)) {
|
||||||
twitchButton.click();
|
twitchButton.click();
|
||||||
}
|
}
|
||||||
@@ -145,16 +142,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SPA Navigation Handler (MutationObserver)
|
// SPA Navigation Handler (MutationObserver)
|
||||||
let mutationTimeout = null;
|
let lastMutate = 0;
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
if (mutationTimeout) clearTimeout(mutationTimeout);
|
const now = Date.now();
|
||||||
mutationTimeout = setTimeout(() => {
|
if (now - lastMutate >= 1000) {
|
||||||
|
lastMutate = now;
|
||||||
const video = findVideo();
|
const video = findVideo();
|
||||||
if (video && !video.dataset.koalaAttached) {
|
if (video && !video.dataset.koalaAttached) {
|
||||||
console.log('KoalaSync: New video detected via navigation.');
|
console.log('KoalaSync: New video detected via navigation.');
|
||||||
setupListeners();
|
setupListeners();
|
||||||
}
|
}
|
||||||
}, 1000); // 1s debounce
|
}
|
||||||
});
|
});
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
|
||||||
|
|||||||
+28
-8
@@ -404,15 +404,35 @@ elements.forceSyncBtn.addEventListener('click', async () => {
|
|||||||
const settings = await chrome.storage.sync.get(['targetTabId']);
|
const settings = await chrome.storage.sync.get(['targetTabId']);
|
||||||
if (!settings.targetTabId) return;
|
if (!settings.targetTabId) return;
|
||||||
|
|
||||||
chrome.tabs.sendMessage(parseInt(settings.targetTabId), { action: 'get_current_time' }, (response) => {
|
const tabId = parseInt(settings.targetTabId);
|
||||||
if (response && response.currentTime !== undefined) {
|
|
||||||
const time = parseFloat(response.currentTime);
|
const sendForceSync = (time) => {
|
||||||
chrome.runtime.sendMessage({
|
chrome.runtime.sendMessage({
|
||||||
type: 'CONTENT_EVENT',
|
type: 'CONTENT_EVENT',
|
||||||
action: EVENTS.FORCE_SYNC_PREPARE,
|
action: EVENTS.FORCE_SYNC_PREPARE,
|
||||||
payload: { targetTime: time }
|
payload: { targetTime: parseFloat(time) }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
chrome.tabs.sendMessage(tabId, { action: 'get_current_time' }, (response) => {
|
||||||
|
if (chrome.runtime.lastError || !response || response.currentTime === undefined) {
|
||||||
|
chrome.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
files: ['content.js']
|
||||||
|
}).then(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
chrome.tabs.sendMessage(tabId, { action: 'get_current_time' }, (retryResponse) => {
|
||||||
|
if (retryResponse && retryResponse.currentTime !== undefined) {
|
||||||
|
sendForceSync(retryResponse.currentTime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}).catch(() => {
|
||||||
|
showError('Could not connect to video tab.');
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
sendForceSync(response.currentTime);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -450,7 +470,7 @@ async function refreshLogs() {
|
|||||||
if (logs) {
|
if (logs) {
|
||||||
elements.logList.innerHTML = logs.map(log => `
|
elements.logList.innerHTML = logs.map(log => `
|
||||||
<div class="log-entry log-${log.type}">
|
<div class="log-entry log-${log.type}">
|
||||||
[${log.timestamp.split('T')[1].split('.')[0]}] ${log.message}
|
[${log.timestamp.split('T')[1].split('.')[0]}] ${escapeHtml(log.message)}
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ io.on('connection', (socket) => {
|
|||||||
log('CONN', `New connection: ${socket.id} from ${clientIp}`);
|
log('CONN', `New connection: ${socket.id} from ${clientIp}`);
|
||||||
|
|
||||||
socket.on(EVENTS.JOIN_ROOM, async ({ roomId, password, peerId, protocolVersion }) => {
|
socket.on(EVENTS.JOIN_ROOM, async ({ roomId, password, peerId, protocolVersion }) => {
|
||||||
|
if (typeof roomId !== 'string') return;
|
||||||
try {
|
try {
|
||||||
// Protocol check
|
// Protocol check
|
||||||
if (protocolVersion !== '1.0.0') {
|
if (protocolVersion !== '1.0.0') {
|
||||||
|
|||||||
Reference in New Issue
Block a user