From d59fc4777d4c2cb79da17bc1da5de5d5644f0664 Mon Sep 17 00:00:00 2001 From: Timo <6156589+Shik3i@users.noreply.github.com> Date: Thu, 4 Jun 2026 15:18:57 +0200 Subject: [PATCH] chore: release v2.1.0 with stabilization and audit fixes --- CHANGELOG.md | 12 + extension/background.js | 17 +- extension/bridge.js | 4 +- extension/content.js | 11 +- extension/i18n.js | 43 +++- extension/popup.html | 2 +- extension/popup.js | 1 + website/app.js | 83 +++++- website/build.js | 24 +- website/datenschutz-de.html | 169 +++++++++++++ website/datenschutz.html | 236 ++---------------- website/impressum-de.html | 156 ++++++++++++ website/impressum.html | 210 ++-------------- website/imprint.html | 154 ++++++++++++ website/lang-init.js | 25 +- website/locales/de.json | 4 +- website/locales/en.json | 4 +- website/locales/es.json | 4 +- website/locales/fr.json | 4 +- website/locales/it.json | 6 +- website/locales/ja.json | 6 +- website/locales/ko.json | 6 +- website/locales/nl.json | 6 +- website/locales/pl.json | 6 +- website/locales/pt-BR.json | 4 +- website/locales/pt.json | 6 +- website/locales/ru.json | 4 +- website/locales/tr.json | 6 +- website/privacy.html | 169 +++++++++++++ website/sitemap.xml | 194 +++++++++++++- website/style.css | 4 + website/template.html | 4 +- website/www/app.877f29ac.min.js | 59 +++++ website/www/app.bd666530.min.js | 59 ----- website/www/datenschutz.html | 236 ++---------------- website/www/de/datenschutz.html | 169 +++++++++++++ website/www/de/impressum.html | 156 ++++++++++++ website/www/de/index.html | 12 +- website/www/es/index.html | 12 +- website/www/fr/index.html | 12 +- website/www/impressum.html | 210 ++-------------- website/www/imprint.html | 154 ++++++++++++ website/www/index.html | 12 +- website/www/it/index.html | 12 +- website/www/ja/index.html | 12 +- website/www/join.html | 8 +- website/www/ko/index.html | 12 +- website/www/lang-init.1b13bbf2.min.js | 1 + website/www/lang-init.efcb118a.min.js | 1 - website/www/nl/index.html | 12 +- website/www/pl/index.html | 12 +- website/www/privacy.html | 169 +++++++++++++ website/www/pt-BR/index.html | 12 +- website/www/pt/index.html | 12 +- website/www/ru/index.html | 12 +- website/www/sitemap.xml | 194 +++++++++++++- ...ca56f45.min.css => style.39c35bf7.min.css} | 2 +- website/www/tr/index.html | 12 +- 58 files changed, 2119 insertions(+), 1039 deletions(-) create mode 100644 website/datenschutz-de.html create mode 100644 website/impressum-de.html create mode 100644 website/imprint.html create mode 100644 website/privacy.html create mode 100644 website/www/app.877f29ac.min.js delete mode 100644 website/www/app.bd666530.min.js create mode 100644 website/www/de/datenschutz.html create mode 100644 website/www/de/impressum.html create mode 100644 website/www/imprint.html create mode 100644 website/www/lang-init.1b13bbf2.min.js delete mode 100644 website/www/lang-init.efcb118a.min.js create mode 100644 website/www/privacy.html rename website/www/{style.3ca56f45.min.css => style.39c35bf7.min.css} (72%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 020ffa0..681cf26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,18 @@ All notable changes to the KoalaSync browser extension and relay server. - Added flag emojis to language selector dropdowns in both the extension popup and landing/utility web pages for quicker visual identification. - Added 181 translation keys parity validation suite checks for the new languages. +### Fixed & Hardened (Extension Audit) +- Guarded all website `localStorage` interactions to prevent initialization/join flow script failures on privacy-hardened or cookie-blocked browser configurations. +- Added robust validation null-guards to `chrome.runtime.onMessage` listeners across all extension scripts (`bridge.js`, `content.js`, `background.js`, `popup.js`) to reject unexpected runtime messages. +- Guarded CustomEvent payload destructuring in `bridge.js` to ensure stability when receiving third-party page events. +- Wrapped `video.currentTime` seeking adjustments during forced sync in content scripts with exception handling to absorb uninitialized video state DOMExceptions. +- Added payload validation guards on incoming Socket.IO events within the background script's event handlers to secure against malformed server updates. +- Prevented noisy browser console exceptions from context invalidation in target tabs by catching promise rejections on extension message dispatches. + +### Performance +- Implemented in-memory language dictionary caching in the background script to completely avoid redundant extension package filesystem reads during translations. + + --- ## [v2.0.8] — 2026-06-03 diff --git a/extension/background.js b/extension/background.js index 09e0caf..46a4c33 100644 --- a/extension/background.js +++ b/extension/background.js @@ -690,6 +690,10 @@ function addToHistory(action, senderId) { // --- Event Handlers --- function handleServerEvent(event, data) { + if (!data) { + addLog(`Ignored server event ${event} due to empty payload`, 'warn'); + return; + } switch (event) { case EVENTS.ROOM_DATA: currentRoom = data; @@ -1292,6 +1296,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { }); async function handleAsyncMessage(message, sender, sendResponse) { + if (!message) return; await ensureState(); if (message.type === 'CONNECT') { @@ -1448,15 +1453,19 @@ async function handleAsyncMessage(message, sender, sendResponse) { localSeq++; chrome.storage.session.set({ localSeq }); updateLastAction(message.action, 'You', timestamp); - lastActionState.targetTime = message.payload?.targetTime !== undefined ? message.payload.targetTime : message.payload?.currentTime; + + const payload = message.payload || {}; + lastActionState.targetTime = payload.targetTime !== undefined ? payload.targetTime : payload.currentTime; if (storageInitialized) chrome.storage.session.set({ lastActionState }); - message.payload.actionTimestamp = timestamp; - message.payload.seq = localSeq; + + payload.actionTimestamp = timestamp; + payload.seq = localSeq; + message.payload = payload; // Local Reactive Update updateLocalPeerState(peerId, { playbackState: message.action === EVENTS.PLAY ? 'playing' : (message.action === EVENTS.PAUSE ? 'paused' : undefined), - currentTime: message.payload.currentTime !== undefined ? message.payload.currentTime : (message.payload.targetTime !== undefined ? message.payload.targetTime : undefined) + currentTime: payload.currentTime !== undefined ? payload.currentTime : (payload.targetTime !== undefined ? payload.targetTime : undefined) }); if (message.action === EVENTS.FORCE_SYNC_PREPARE) { diff --git a/extension/bridge.js b/extension/bridge.js index cbc56f0..d4584cb 100644 --- a/extension/bridge.js +++ b/extension/bridge.js @@ -10,6 +10,7 @@ document.documentElement.dataset.koalasyncInstalled = 'true'; // 2. Listen for Join Requests from the Website window.addEventListener('KOALASYNC_JOIN_REQUEST', (e) => { + if (!e || !e.detail) return; const { roomId, password, useCustomServer, serverUrl } = e.detail; chrome.runtime.sendMessage({ type: 'WEB_JOIN_REQUEST', @@ -17,11 +18,12 @@ window.addEventListener('KOALASYNC_JOIN_REQUEST', (e) => { password, useCustomServer, serverUrl - }); + }).catch(() => {}); }); // 3. Listen for Status Updates from the Extension and relay to Website chrome.runtime.onMessage.addListener((msg) => { + if (!msg) return; if (msg.type === 'JOIN_STATUS') { const detail = { success: msg.success, message: msg.message }; // Firefox MV3 content scripts run in an isolated world. When dispatching diff --git a/extension/content.js b/extension/content.js index ba8d37d..1d0824d 100644 --- a/extension/content.js +++ b/extension/content.js @@ -344,6 +344,7 @@ // Listen for commands from background.js chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (!message) return; if (message.action === 'get_current_time') { const video = findVideo(); sendResponse({ currentTime: video ? video.currentTime : null }); @@ -366,7 +367,7 @@ if (isDifferentEpisode(senderTitle, myTitle)) { reportLog(`Episode mismatch: sender="${senderTitle || '?'}" vs mine="${myTitle || '?'}" — skipping ${action}. Disable "Auto-Sync next Episode" in settings if this causes issues.`, 'warn'); if (action !== EVENTS.FORCE_SYNC_PREPARE && action !== EVENTS.FORCE_SYNC_EXECUTE) { - chrome.runtime.sendMessage({ type: 'CMD_ACK', actionTimestamp: message.actionTimestamp, commandSenderId: message.commandSenderId }); + chrome.runtime.sendMessage({ type: 'CMD_ACK', actionTimestamp: message.actionTimestamp, commandSenderId: message.commandSenderId }).catch(() => {}); } return; } @@ -395,7 +396,11 @@ _setSuppress('paused'); _setSuppress('seek'); video.pause(); - video.currentTime = payload.targetTime; + try { + video.currentTime = payload.targetTime; + } catch (e) { + reportLog(`Force Sync Seek Error: ${e.message}`, 'error'); + } pollSeekReady(payload.targetTime).then((ready) => { chrome.runtime.sendMessage({ type: 'FORCE_SYNC_ACK' }).catch(() => {}); if (ready) { @@ -622,7 +627,7 @@ mediaTitle: mediaTitle, timestamp: Date.now() } - }); + }).catch(() => {}); // Trigger proactive heartbeat to push stabilized state scheduleProactiveHeartbeat(); diff --git a/extension/i18n.js b/extension/i18n.js index 7ed4d79..9bfb2c3 100644 --- a/extension/i18n.js +++ b/extension/i18n.js @@ -3,6 +3,8 @@ export const SUPPORTED_LANGUAGES = ['en', 'de', 'fr', 'es', 'pt-BR', 'ru', 'it', export const DEFAULT_LANGUAGE = 'en'; let activeDictionary = {}; +const dictionaryCache = {}; +let currentLanguage = null; /** * Resolves, loads, and merges the target language with the English baseline fallback. @@ -11,13 +13,30 @@ let activeDictionary = {}; export async function loadLocale(langCode) { const resolvedLang = SUPPORTED_LANGUAGES.includes(langCode) ? langCode : DEFAULT_LANGUAGE; + if (currentLanguage === resolvedLang && Object.keys(activeDictionary).length > 0) { + return; + } + + if (dictionaryCache[resolvedLang]) { + activeDictionary = dictionaryCache[resolvedLang]; + currentLanguage = resolvedLang; + return; + } + try { // Load Baseline English - const enResponse = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`)); - const enDict = await enResponse.json(); + let enDict; + if (dictionaryCache[DEFAULT_LANGUAGE]) { + enDict = dictionaryCache[DEFAULT_LANGUAGE]; + } else { + const enResponse = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`)); + enDict = await enResponse.json(); + dictionaryCache[DEFAULT_LANGUAGE] = enDict; + } if (resolvedLang === DEFAULT_LANGUAGE) { activeDictionary = enDict; + currentLanguage = resolvedLang; return; } @@ -26,15 +45,27 @@ export async function loadLocale(langCode) { const targetDict = await targetResponse.json(); // Airtight Fallback Merge: target overrides en, missing elements fallback to en - activeDictionary = Object.assign({}, enDict, targetDict); + const mergedDict = Object.assign({}, enDict, targetDict); + dictionaryCache[resolvedLang] = mergedDict; + activeDictionary = mergedDict; + currentLanguage = resolvedLang; } catch (err) { console.error('[i18n] Failed to load locale. Defaulting to English:', err); // Fallback directly to static English if fetching fails try { - const enResponse = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`)); - activeDictionary = await enResponse.json(); + let enDict; + if (dictionaryCache[DEFAULT_LANGUAGE]) { + enDict = dictionaryCache[DEFAULT_LANGUAGE]; + } else { + const enResponse = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`)); + enDict = await enResponse.json(); + dictionaryCache[DEFAULT_LANGUAGE] = enDict; + } + activeDictionary = enDict; + currentLanguage = DEFAULT_LANGUAGE; } catch (_) { activeDictionary = {}; + currentLanguage = null; } } } @@ -46,7 +77,7 @@ export async function loadLocale(langCode) { * @returns {string} Translated string or the key itself */ export function getMessage(key, placeholders = null) { - let msg = activeDictionary[key] || key; + let msg = activeDictionary[key] !== undefined ? String(activeDictionary[key]) : key; if (placeholders && typeof placeholders === 'object') { for (const [k, v] of Object.entries(placeholders)) { msg = msg.replace(new RegExp(`{${k}}`, 'g'), v); diff --git a/extension/popup.html b/extension/popup.html index 1bfb22d..cf6f225 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -497,7 +497,7 @@