Compare commits

..

6 Commits

Author SHA1 Message Date
Timo d76e9195c4 fix(server): race condition on concurrent peer joins, crash-safe teardown, smart unhandled rejection handling
- Add per-peerId serialization lock (peerJoinLocks) to prevent concurrent dedupe races

- Wrap removePeerFromRoom calls in disconnect/leave/reaper with try/catch

- Replace immediate process.exit on unhandledRejection with rate-limited smart exit

- Optimize buildHealthPayload from 3-pass array ops to single for-of loop

- Reset rateLimitDenied counters in stopServerForTests

Release v2.3.1
2026-06-15 13:14:00 +02:00
KoalaDev 6652a06840 docs: correct rationale for english and german translations 2026-06-15 02:56:37 +02:00
KoalaDev b1a89cff41 docs: restructure TRANSLATION.md to prioritize extension and remove outdated details 2026-06-15 02:55:00 +02:00
KoalaDev e4a77a3ef4 docs: improve contribution and translation guidelines 2026-06-15 02:47:17 +02:00
KoalaDev 5adcce2074 Release v2.3.0 with new onboarding tour and sync tab
Updated CHANGELOG for v2.3.0 release with new features.
2026-06-14 05:31:27 +02:00
GitHub Action a1398ed0e4 chore(release): update versions to v2.3.0 [skip ci] 2026-06-14 03:17:46 +00:00
14 changed files with 218 additions and 444 deletions
+27 -15
View File
@@ -12,7 +12,7 @@ Please note that by participating in this project, you agree to abide by our [Co
|------|-------------|
| **Bug Reports** | Found a bug? Open an issue with repro steps (see template below). |
| **Code** | Fix bugs, add features, or improve the extension / server / website. |
| **Translations** | Help localize the extension and website into more languages. See [TRANSLATION.md](website/TRANSLATION.md). |
| **Translations** | Help localize the extension and website into more languages. See [TRANSLATION.md](docs/TRANSLATION.md). |
| **Documentation** | Improve docs, fix typos, or add missing examples. |
| **Security** | Found a vulnerability? See [SECURITY.md](SECURITY.md) — do NOT open a public issue. |
@@ -100,15 +100,23 @@ KoalaSync uses a **single source of truth** for all protocol constants in `share
---
## Pull Request Process
## Open Source Workflow & Pull Requests
1. **Branch** from `main` for your feature or fix.
2. **Test locally** on both Chrome and Firefox.
3. **Website changes**: Run `node website/build.js` and verify the compiled output in `www/`.
4. **Lint**: Ensure `npm run lint` passes with zero errors and zero warnings.
5. **Syntax**: Run `node -c` on every modified `.js` file.
6. **Protocol changes**: Update relevant documentation in `docs/`.
7. **Submit your PR** with a clear description and linked issue (if applicable).
If you are new to open-source contributions, follow these steps to propose your changes:
1. **Fork the Repository**: Click the "Fork" button at the top right of this repository to create your own copy of KoalaSync.
2. **Clone your Fork**: `git clone https://github.com/YOUR-USERNAME/KoalaSync.git`
3. **Create a Branch**: `git checkout -b my-new-feature` (e.g. `feature/dark-mode` or `fix/translation-de`)
4. **Make your Changes**: Edit the files, then verify them locally.
- *Extension/Server changes*: Test on Chrome/Firefox and check `npm run lint`.
- *Website/Translation changes*: Run `node website/build.js` and check the output in `www/`.
5. **Commit and Push**: `git commit -m "Add my feature"` and `git push origin my-new-feature`
6. **Open a Pull Request (PR)**: Go to the original KoalaSync repository on GitHub and click "New Pull Request".
### PR Code Requirements
- **Lint**: Ensure `npm run lint` passes with zero errors and warnings.
- **Syntax**: Run `node -c` on every modified `.js` file.
- **Protocol changes**: Update relevant documentation in `docs/`.
---
@@ -140,14 +148,18 @@ If you cannot access the Status tab, include as much of the following manually:
---
## Translation Contributions
## Translation Contributions (Translators Welcome!)
KoalaSync supports 6 languages: English, German, French, Spanish, Portuguese (Brazilian), and Russian.
We welcome native speakers to help translate KoalaSync! You **do not** need deep programming knowledge to contribute translations.
To add or improve translations:
1. Edit the locale files in `website/locales/` (for the website).
2. For extension translations, see [TRANSLATION.md](website/TRANSLATION.md).
3. Run `node website/build.js` to regenerate the static site.
KoalaSync supports multiple languages. To add or improve translations:
1. Read the **[Translation Guide](docs/TRANSLATION.md)** first. It explains how our localization system works.
2. Edit the `.json` files in `website/locales/` (for the website) and `extension/locales/` (for the extension).
3. Test your translations locally by running:
- `node scripts/test-locales.js` (for extension)
- `node scripts/test-website-locales.mjs` (for website)
- `node website/build.js` (to build the site)
4. Follow the **Open Source Workflow** above (Fork -> Branch -> Edit -> PR) to submit your translations.
---
+4 -4
View File
@@ -14,7 +14,7 @@
<p align="center"><i>KoalaSync is a lightweight Browser Extension and Relay Server for synchronized video playback on almost any website with a video element—YouTube, Twitch, Netflix, Emby, Jellyfin, and beyond. Built with a focus on <b>Data Sovereignty</b> and <b>Performance</b>.</i></p>
<p align="center"><a href="docs/CHANGELOG.md"><b>New v2.2.4 Release!</b> — See what's changed</a></p>
<p align="center"><a href="docs/CHANGELOG.md"><b>New v2.3.0 Release!</b> — See what's changed</a></p>
### 🌟 Why KoalaSync?
@@ -61,7 +61,7 @@ The easiest and safest way to install KoalaSync is directly through the official
Both the official KoalaSync website and the **v2.0 Browser Extension** feature full dynamic localization:
- **Available Languages**: Support is included for 13 languages: English (`en`), German (`de`), French (`fr`), Spanish (`es`), Portuguese (Brazil) (`pt-BR`), Russian (`ru`), Italian (`it`), Polish (`pl`), Turkish (`tr`), Dutch (`nl`), Japanese (`ja`), Korean (`ko`), and European Portuguese (`pt`).
- **Real-Time Extension Localization**: Inside the extension Settings panel, users can swap languages instantly. The entire interface, notifications, Empty States, and onboarding guides re-translate dynamically in real-time.
- **Contributing**: We welcome community translations for both the website and the extension! Please refer directly to the [TRANSLATION.md](website/TRANSLATION.md) guide for step-by-step instructions on how to audit, refine, or add new languages.
- **Contributing**: We welcome community translations for both the website and the extension! Please refer directly to the [TRANSLATION.md](docs/TRANSLATION.md) guide for step-by-step instructions on how to audit, refine, or add new languages.
---
@@ -103,7 +103,7 @@ To connect your extension to a self-hosted server, open the popup → **Room** t
To verify your relay is reachable from outside, visit `https://your-domain.com` in a browser — it should return `{"status":"online","service":"KoalaSync Relay"}`.
#### Supply Chain Security (v2.2.4+)
#### Supply Chain Security (v2.3.0+)
All official release artifacts (Docker images and extension binaries) are published with signed [artifact attestations](https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations) to prove they were built from this repository's source code.
@@ -125,7 +125,7 @@ gh attestation verify dist/koalasync-chrome.zip \
- **[CHANGELOG.md](docs/CHANGELOG.md)**: Full version history for the extension and relay server.
- **[TESTED_SERVICES.md](docs/TESTED_SERVICES.md)**: Detailed compatibility matrix of tested streaming platforms and known limitations.
- **[TRANSLATION.md](website/TRANSLATION.md)**: Translation and localization guide for contributors.
- **[TRANSLATION.md](docs/TRANSLATION.md)**: Translation and localization guide for contributors.
- **[PRIVACY.md](docs/PRIVACY.md)**: Data Handling and Privacy Policy.
- **[CONTRIBUTING.md](CONTRIBUTING.md)**: How to help make KoalaSync better.
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)**: Our community standards and expectations.
+12 -1
View File
@@ -4,7 +4,18 @@ All notable changes to the KoalaSync browser extension and relay server.
---
## [v2.3.0] — Unreleased
## [v2.3.1] — 2026-06-15
### Fixed
- **Server: Race condition on concurrent peer joins**: Added a per-peerId serialization lock (`peerJoinLocks`) that prevents two connections with the same `peerId` from both passing the deduplication check and simultaneously registering in `peerToSocket`. Previously, rapid reconnects could leave stale mappings that caused cross-room ACK/PING misdelivery.
- **Server: Crash-safe error handling in teardown paths**: Wrapped `removePeerFromRoom` calls in the disconnect, leave, and reaper handlers with try/catch to prevent an unhandled exception in any teardown path from crashing the process.
- **Server: Smart unhandled rejection handling**: Replaced the immediate `process.exit(1)` on any `unhandledRejection` with a rate-limited approach — the server now exits only after 5 unhandled rejections within 60 seconds, surviving transient errors while still failing fast on cascading crashes.
- **Server: Reduced GC pressure in admin health metrics**: Replaced the three-pass `Array.from()` / `map()` / `reduce()` / `filter()` pattern in `buildHealthPayload()` with a single `for-of` loop, eliminating temporary array allocations proportional to the number of active rooms.
- **Server: Test isolation for rate-limit denial counters**: `stopServerForTests()` now resets the `rateLimitDenied` counters between test runs.
---
## [v2.3.0] — 2026-06-14
### Added
- **Extension: New Interactive Onboarding Tour**: A fully redesigned, interactive step-by-step onboarding experience.
+1
View File
@@ -5,3 +5,4 @@ This directory contains deep-dives into the KoalaSync protocol and architecture.
- [HOW_IT_WORKS.md](HOW_IT_WORKS.md): Step-by-step walkthrough of every user flow, from room creation to synchronized playback. Ideal for store reviewers and manual testers.
- [ARCHITECTURE.md](ARCHITECTURE.md): Communication flows, Dual Heartbeat, and Sync logic.
- [SYNC_GUIDE.md](SYNC_GUIDE.md): Protocol constants and sync requirements.
- [TRANSLATION.md](TRANSLATION.md): Translation and localization guide for the extension and website.
+105
View File
@@ -0,0 +1,105 @@
# KoalaSync Translation & Localization Guide
Welcome to the **KoalaSync** translation guide! We rely on the open-source community to make KoalaSync accessible to users worldwide.
KoalaSync is split into two independent translation areas. You can translate either one, or both:
1. **The Browser Extension** (`extension/locales/`): The core product that users interact with daily.
2. **The Website** (`website/locales/`): The landing page and invitation bridge.
---
## 📊 Supported Languages Dashboard
We divide supported languages into two tiers: **Core Languages** (fully hand-crafted and audited by native speakers) and **Extended Languages** (auto-generated using translation models to expand initial coverage).
> [!TIP]
> **Help Us Improve!**
> We welcome community contributions to audit "Auto-Generated" translations and elevate them to "Verified" status.
| Language Code | Language Name | Verification Status | Rationale / Context |
| :--- | :--- | :--- | :--- |
| `en` | 🇬🇧 **English** | `100% Manually Verified` | Global default language (Verified by developer) |
| `de` | 🇩🇪 **German** | `100% Manually Verified` | Developer's native language |
| `fr` | 🇫🇷 **French** | `Auto-Generated` | Needs manual native review and polishing |
| `es` | 🇪🇸 **Spanish** | `Auto-Generated` | Needs manual native review and polishing |
| `pt-BR` | 🇧🇷 **Portuguese (Brasil)** | `Auto-Generated` | Needs manual native review and polishing |
| `ru` | 🇷🇺 **Russian** | `Auto-Generated` | Needs manual native review and polishing |
| `it` | 🇮🇹 **Italian** | `Auto-Generated` | Needs manual native review and polishing |
| `pl` | 🇵🇱 **Polish** | `Auto-Generated` | Needs manual native review and polishing |
| `tr` | 🇹🇷 **Turkish** | `Auto-Generated` | Needs manual native review and polishing |
| `nl` | 🇳🇱 **Dutch** | `Auto-Generated` | Needs manual native review and polishing |
| `ja` | 🇯🇵 **Japanese** | `Auto-Generated` | Needs manual native review and polishing |
| `ko` | 🇰🇷 **Korean** | `Auto-Generated` | Needs manual native review and polishing |
| `pt` | 🇵🇹 **European Portuguese** | `Auto-Generated` | Needs manual native review and polishing |
> [!WARNING]
> **Autogeneration Quality Rule**
> Any newly contributed languages must be marked as `"Auto-Generated"` in this table until fully reviewed and signed off by a native speaker in a pull request.
---
## 🛠️ How to Translate KoalaSync
Here is the exact step-by-step process for contributing translations.
### Step 1: Fork and Clone the Repository
If you are an external contributor, start with the standard Open Source workflow:
1. Click the "Fork" button on GitHub to create your own copy of the repository.
2. Clone your fork locally: `git clone https://github.com/YOUR-USERNAME/KoalaSync.git`
3. Create a branch: `git checkout -b translation/my-language`
### Step 2: Translate the Extension
The browser extension handles real-time syncing, settings, and popups.
1. Navigate to [`extension/locales/`](file:///Users/koala/Documents/Workspaces/KoalaSync/extension/locales/).
2. Edit an existing `[lang].json` or copy `en.json` to create a new one (e.g., `it.json`).
3. Translate all the string values. **Do not change the JSON keys.**
### Step 3: Translate the Website
The website hosts the landing page and invitation bridge.
1. Navigate to [`website/locales/`](file:///Users/koala/Documents/Workspaces/KoalaSync/website/locales/).
2. Edit an existing `[lang].json` or copy `en.json` to create a new one.
3. Translate all the string values. **Do not change the JSON keys.**
4. If creating a **brand new language**, configure the metadata at the top of your JSON file:
```json
{
"LANG_CODE": "it",
"HTML_CLASS": "lang-it",
"CANONICAL_PATH": "it/",
"LANG_TOGGLE_URL": "../",
"LANG_TOGGLE_TEXT": "EN"
}
```
5. If creating a **brand new language**, register it in `website/build.js` by adding it to the `languages` array.
### Step 4: Verify Locally
Ensure your JSON files are valid and all keys match the English baseline. Open your terminal in the KoalaSync root folder and run:
```bash
# Tests the extension locales for missing keys or syntax errors
node scripts/test-locales.js
# Tests the website locales for missing keys or syntax errors
node scripts/test-website-locales.mjs
# Builds the website with your new translations
node website/build.js
```
*Note: If you receive any errors about missing keys or `TODO` placeholders, please fix them before submitting.*
### Step 5: Commit and Pull Request
1. Open this `TRANSLATION.md` file and add/update your language in the **Supported Languages Dashboard** above. Mark it as `100% Manually Verified` if you are a native speaker.
2. Commit your changes: `git commit -m "Update Italian translations"`
3. Push to your fork: `git push origin translation/my-language`
4. Open a **Pull Request** on the main KoalaSync repository on GitHub.
---
## ⚖️ Strict Legal Exclusion Rule
Our legal pages have strict constraints to protect user privacy and avoid regulatory liabilities.
> [!IMPORTANT]
> **DO NOT TRANSLATE LEGAL DOCUMENTS**
> The legal notice ([impressum.html](file:///Users/koala/Documents/Workspaces/KoalaSync/website/impressum.html)) and privacy policy ([datenschutz.html](file:///Users/koala/Documents/Workspaces/KoalaSync/website/datenschutz.html)) **MUST remain exclusively in English and German**.
>
> * **Rationale:** Legal compliance under the European Union General Data Protection Regulation (GDPR) and the German Digital Services Act (DDG). Offering automated translations of legally binding notices introduces compliance risks due to potential mistranslations of liability limits.
> * **Technical Fallback:** Our system automatically falls back to **English** for legal pages if a user visits them in an unsupported language, so you do not need to worry about this.
-204
View File
@@ -1,204 +0,0 @@
# KoalaSync Browser Extension v2.0 - i18n Technical Implementation Plan
Welcome, future Antigravity AI agent! This document is placed directly in the codebase at `/extension/locales/i18n_plan.md` to serve as a comprehensive architectural handbook for the next steps in adding **full internationalization (i18n) support to the browser extension itself** while maintaining 100% video-sync and background communication safety.
---
## 🔍 Context & Audited Scope
KoalaSync is a lightweight, premium browser extension (Chrome & Firefox Manifest V3) for synchronized video playback. The landing pages are already compiled dynamically in 6 languages:
* English (`en`)
* German (`de`)
* French (`fr`)
* Spanish (`es`)
* Portuguese (Brazil) (`pt-BR`)
* Russian (`ru`)
Our goal is to build an identical, premium translation engine for the extension itself.
---
## 🏛️ Architectural Choice: Custom JSON Dictionary Engine (Approach B)
We evaluated two paths:
1. **Approach A (Native `chrome.i18n` API):** Uses `_locales/` directory. Too rigid—cannot support real-time dynamic switching inside the extension Settings dropdown without closing/re-opening the popup.
2. **Approach B (Custom Unified JSON Engine):** Uses flat JSON files matching our website files (`"KEY": "Value"`). Dynamically merges the target dictionary with baseline English `en.json` at runtime, programmatically providing an **airtight English fallback** and **real-time DOM translations** without popup reload.
We chose **Approach B** for maximum compatibility, premium real-time toggling, and clean fallback safety.
---
## 🔄 Resolve, Load, & State Flow
1. **On launch:** Look for saved language in `chrome.storage.sync.get('locale')`.
2. **Fallback Autodetect:** If no saved language, read `navigator.language` or `chrome.i18n.getUILanguage()`.
* If the detected locale is supported, set as active.
* If not supported, default to English (`en`).
3. **Dictionary Resolution:**
* Asynchronously load the English baseline dictionary (`/extension/locales/en.json`).
* If the target language is different, load target JSON (e.g. `/extension/locales/de.json`) and execute `Object.assign({}, enDict, targetDict)`. This guarantees dynamic translation while cleanly falling back to English for any missing keys.
4. **DOM Replacements:** Scan for `data-i18n`, `data-i18n-title`, and `data-i18n-placeholder` attributes, and translate them on the fly.
5. **Persistence:** Save dynamic selection modifications from the dropdown into `chrome.storage.sync`. Trigger instant DOM re-translation on change.
---
## 📂 Proposed File Structure
```
KoalaPlay/
└── extension/
├── locales/ # [NEW] Contains flat translation maps
│ ├── i18n_plan.md # This roadmap file
│ ├── en.json # Flat English baseline keys
│ ├── de.json # German keys
│ ├── fr.json # French keys
│ ├── es.json # Spanish keys
│ ├── pt-BR.json # Portuguese (Brasil) keys
│ └── ru.json # Russian keys
├── i18n.js # [NEW] ESM translation engine module
├── popup.html # Modified with data-i18n attributes
├── popup.js # Modified to initialize locales and update variables
└── background.js # Modified to push localized notification alerts
```
---
## 🛠️ Draft Code Snippets
### 1. `i18n.js` (Zero-Dependency Engine Module)
```javascript
// extension/i18n.js
export const SUPPORTED_LANGUAGES = ['en', 'de', 'fr', 'es', 'pt-BR', 'ru'];
export const DEFAULT_LANGUAGE = 'en';
let activeDictionary = {};
export async function loadLocale(langCode) {
const resolvedLang = SUPPORTED_LANGUAGES.includes(langCode) ? langCode : DEFAULT_LANGUAGE;
try {
const enResponse = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`));
const enDict = await enResponse.json();
if (resolvedLang === DEFAULT_LANGUAGE) {
activeDictionary = enDict;
return;
}
const targetResponse = await fetch(chrome.runtime.getURL(`locales/${resolvedLang}.json`));
const targetDict = await targetResponse.json();
activeDictionary = Object.assign({}, enDict, targetDict);
} catch (err) {
console.error('[i18n] Failed to load dictionary. Falling back to English:', err);
const rescue = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`));
activeDictionary = await rescue.json();
}
}
export function getMessage(key) {
return activeDictionary[key] || key;
}
export function translateDOM() {
// Translate text nodes
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
const translated = getMessage(key);
const img = el.querySelector('img');
if (img) {
el.innerHTML = '';
el.appendChild(img);
el.appendChild(document.createTextNode(' ' + translated));
} else {
el.textContent = translated;
}
});
// Translate tooltips
document.querySelectorAll('[data-i18n-title]').forEach(el => {
const key = el.getAttribute('data-i18n-title');
el.setAttribute('title', getMessage(key));
});
// Translate placeholders
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
const key = el.getAttribute('data-i18n-placeholder');
el.setAttribute('placeholder', getMessage(key));
});
}
```
### 2. Markup Changes (`popup.html`)
* Annotate text elements: `<button class="tab-btn" data-tab="tab-settings" data-i18n="TAB_SETTINGS" data-i18n-title="TAB_SETTINGS_TOOLTIP">Settings</button>`
* Add Language Dropdown in the Settings Panel:
```html
<div class="form-group" style="display: flex; align-items: center; justify-content: space-between; background: var(--card); padding: 10px; border-radius: 8px; margin-bottom: 12px; border: 1px solid #334155;">
<label style="margin-bottom: 0;" data-i18n="LABEL_LANGUAGE" title="Choose your preferred extension language">App Language</label>
<select id="langSelector" style="width: 150px; padding: 6px 10px; font-size: 13px; cursor: pointer;">
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
<option value="es">Español</option>
<option value="pt-BR">Português (Brasil)</option>
<option value="ru">Русский</option>
</select>
</div>
```
### 3. Dynamic Script Wiring (`popup.js`)
* Import our engine: `import { loadLocale, translateDOM, getMessage } from './i18n.js';`
* Initialize during startup:
```javascript
async function init() {
const data = await chrome.storage.sync.get(['locale', ...]);
let activeLang = data.locale;
if (!activeLang) {
const systemLang = (navigator.language || chrome.i18n.getUILanguage()).split('-')[0];
activeLang = ['en', 'de', 'fr', 'es', 'pt', 'ru'].includes(systemLang) ? (systemLang === 'pt' ? 'pt-BR' : systemLang) : 'en';
chrome.storage.sync.set({ locale: activeLang });
}
await loadLocale(activeLang);
translateDOM();
// Select matching option in selector
const langSelector = document.getElementById('langSelector');
if (langSelector) langSelector.value = activeLang;
// rest of standard init...
}
```
* Listen to Settings change event:
```javascript
const langSelector = document.getElementById('langSelector');
if (langSelector) {
langSelector.addEventListener('change', async () => {
const selectedLang = langSelector.value;
await chrome.storage.sync.set({ locale: selectedLang });
await loadLocale(selectedLang);
translateDOM();
// Re-render empty elements and tab list using new dynamic strings
refreshLogs();
refreshHistory();
populateTabs();
});
}
```
### 4. Background Notifications (`background.js`)
* Notifications inside `showNotification` should fetch dynamic keys based on `chrome.storage.sync` language settings and build clean alerts dynamically using `chrome.storage.sync.get('locale')`.
* Ensure that WebSocket event transmissions and video player scripts in `content.js` remain **completely untouched and unaware** of the localization engine.
---
## 🔒 Safety Guardrail
* **WebSocket Engine Integrity:** Under no circumstances should `background.js` Socket.IO handshakes, rate limits, or binary protocol headers be adjusted. i18n is strictly an interface rendering skin layer.
* **Player Control Pipeline Integrity:** `content.js` expectedEvent suppression tables, HLS buffer calculations, and Media Session API listeners must remain untouched. Do not bind any i18n logic into the content script's active tracking loop.
---
## 🧪 Verification Tasks
1. **Autodetection Audit:** Force-load the extension in Chrome/Firefox in a non-English language container and ensure it starts in that language or English gracefully.
2. **Integrity Validation Script:** Write a sanity node checker script `/scripts/test-locales.js` to ensure all key structures are uniform across all JSON files.
3. **Real-Time Refresh Verification:** Change dynamic drop-down preferences and verify that every element and alert swaps languages cleanly with zero redraw glitches or page crashes.
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 3,
"default_locale": "en",
"name": "KoalaSync",
"version": "2.2.4",
"version": "2.3.0",
"description": "Synchronize video playback on YouTube, Netflix, Emby, Jellyfin, and any HTML5 site in real-time with friends.",
"permissions": [
"storage",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "koalasync",
"version": "2.2.4",
"version": "2.3.0",
"description": "KoalaSync Build Scripts",
"private": true,
"scripts": {
+52 -14
View File
@@ -115,6 +115,7 @@ export const rooms = new Map();
const socketToRoom = new Map();
const peerToSocket = new Map(); // peerId -> socketId (Global lookup)
const roomCreationLocks = new Map(); // roomId -> Promise (prevents race on room creation)
const peerJoinLocks = new Map(); // peerId -> Promise (prevents race on same peerId joins)
function log(type, message, details = '') {
const debugLogging = process.env.DEBUG_LOGGING === '1';
@@ -469,7 +470,20 @@ io.on('connection', (socket) => {
return;
}
if (!createdByMe) {
let peerLockPromise = peerJoinLocks.get(peerId);
if (peerLockPromise) {
await peerLockPromise;
room = rooms.get(roomId);
if (!room) {
socket.emit(EVENTS.ERROR, { message: "Room no longer exists" });
return;
}
}
let resolvePeerLock;
peerLockPromise = new Promise(resolve => { resolvePeerLock = resolve; });
peerJoinLocks.set(peerId, peerLockPromise);
try {
if (!createdByMe) {
if (room.passwordHash) {
if (!password || hashPassword(password) !== room.passwordHash) {
recordAuthFailure(ip, roomId);
@@ -527,6 +541,10 @@ io.on('connection', (socket) => {
activeLobby: room.activeLobby || null
});
log('ROOM', `Peer ${peerId} joined: ${roomId.substring(0, 3)}***`);
} finally {
peerJoinLocks.delete(peerId);
resolvePeerLock();
}
} catch (err) {
log('ERROR', `Join error for ${socket.id}`, err);
if (socket.connected) {
@@ -644,10 +662,14 @@ io.on('connection', (socket) => {
});
socket.on(EVENTS.LEAVE_ROOM, () => {
const mapping = socketToRoom.get(socket.id);
if (mapping) {
socket.leave(mapping.roomId);
removePeerFromRoom(socket.id, mapping.roomId, 'leave');
try {
const mapping = socketToRoom.get(socket.id);
if (mapping) {
socket.leave(mapping.roomId);
removePeerFromRoom(socket.id, mapping.roomId, 'leave');
}
} catch (err) {
log('ERROR', 'removePeerFromRoom failed in leave', err);
}
});
@@ -723,10 +745,11 @@ io.on('connection', (socket) => {
roomListCooldowns.delete(socket.id);
const mapping = socketToRoom.get(socket.id);
if (mapping) {
// Socket is already disconnected — no need to call socket.leave().
// removePeerFromRoom uses io.to() for notifications, which correctly
// excludes this dead socket since it has already left all rooms.
removePeerFromRoom(socket.id, mapping.roomId, 'disconnect');
try {
removePeerFromRoom(socket.id, mapping.roomId, 'disconnect');
} catch (err) {
log('ERROR', 'removePeerFromRoom failed in disconnect', err);
}
}
});
});
@@ -751,12 +774,14 @@ const roomCleanupInterval = setInterval(() => {
}
}
for (const sid of staleSids) {
// Gracefully evict the socket from the Socket.IO room if it is
// still technically connected (zombie with no heartbeat).
const deadSocket = io.sockets?.sockets?.get(sid);
if (deadSocket) deadSocket.leave(roomId);
log('CLEANUP', `Pruning dead peer from room ${roomId.substring(0, 3)}***`);
removePeerFromRoom(sid, roomId, 'reaper');
try {
removePeerFromRoom(sid, roomId, 'reaper');
} catch (err) {
log('ERROR', 'removePeerFromRoom failed in reaper', err);
}
}
// 2. Prune empty or inactive rooms
@@ -818,6 +843,7 @@ export async function stopServerForTests() {
socketToRoom.clear();
peerToSocket.clear();
roomCreationLocks.clear();
peerJoinLocks.clear();
connectionCounts.clear();
failedAuthAttempts.clear();
eventCounts.clear();
@@ -827,6 +853,7 @@ export async function stopServerForTests() {
healthResponseCache.clear();
io.removeAllListeners();
io.disconnectSockets(true);
Object.assign(rateLimitDenied, { connections: 0, events: 0, health: 0, adminMetricsAuth: 0, roomList: 0 });
if (!httpServer.listening) return;
await new Promise((resolve, reject) => {
httpServer.close((err) => err ? reject(err) : resolve());
@@ -846,8 +873,19 @@ if (isMainModule) {
process.exit(1);
});
let unhandledRejectionCount = 0;
let unhandledRejectionReset = Date.now();
process.on('unhandledRejection', (reason) => {
log('ERROR', `Unhandled rejection: ${reason}`);
process.exit(1);
log('ERROR', `Unhandled rejection: ${reason}`, reason?.stack || '');
const now = Date.now();
if (now - unhandledRejectionReset > 60000) {
unhandledRejectionCount = 0;
unhandledRejectionReset = now;
}
unhandledRejectionCount++;
if (unhandledRejectionCount >= 5) {
log('ERROR', `Too many unhandled rejections (${unhandledRejectionCount}/min) — aborting`);
process.exit(1);
}
});
}
+12 -7
View File
@@ -61,19 +61,24 @@ export function buildHealthPayload({
if (!includeMetrics) return payload;
const roomValues = Array.from(rooms.values());
const roomSizes = roomValues.map(room => room.peers?.size || 0);
const peers = roomSizes.reduce((sum, size) => sum + size, 0);
const maxPeersInRoom = roomSizes.length > 0 ? Math.max(...roomSizes) : 0;
const avgPeersPerRoom = roomSizes.length > 0
? Math.round((peers / roomSizes.length) * 100) / 100
let peers = 0;
let maxPeersInRoom = 0;
let roomsWithLobby = 0;
for (const room of rooms.values()) {
const size = room.peers?.size || 0;
peers += size;
if (size > maxPeersInRoom) maxPeersInRoom = size;
if (room.activeLobby) roomsWithLobby++;
}
const avgPeersPerRoom = rooms.size > 0
? Math.round((peers / rooms.size) * 100) / 100
: 0;
const mem = memoryUsage();
return {
...payload,
peers,
roomsWithLobby: roomValues.filter(room => !!room.activeLobby).length,
roomsWithLobby,
avgPeersPerRoom,
maxPeersInRoom,
rateLimits: {
-194
View File
@@ -1,194 +0,0 @@
# KoalaSync Translation & Localization Guide
Welcome to the **KoalaSync** translation and internationalization framework! This document provides clear, professional instructions for developers and contributors looking to maintain, audit, or add new languages to the official KoalaSync website.
---
## 🏛️ Architecture Overview
The KoalaSync website utilizes a custom, high-performance, zero-dependency static site generator built in Node.js. Instead of using complex client-side translation runtimes or bulky frameworks, localized pages are compiled ahead-of-time (AOT) to maintain lightning-fast page speeds and strict data sovereignty.
* **Template Source:** [`website/template.html`](file:///Users/koala/Documents/KoalaPlay/website/template.html) (Single Source of Truth)
* **Locales Source:** `/website/locales/[lang].json` (Structured JSON translation dictionaries)
* **Build Pipeline:** [`website/build.js`](file:///Users/koala/Documents/KoalaPlay/website/build.js) (Pure Node.js script that compiles pages into `/website/www/`)
---
## 📊 Supported Languages Dashboard
We divide supported languages into two tiers: **Core Languages** (fully hand-crafted and audited by native speakers) and **Extended Languages** (auto-generated using translation models to expand initial coverage).
> [!TIP]
> **Help Us Improve!**
> We welcome community contributions to audit "Auto-Generated" translations and elevate them to "Verified" status.
| Language Code | Language Name | Verification Status | Rationale / Context |
| :--- | :--- | :--- | :--- |
| `en` | 🇬🇧 **English** | `100% Manually Verified` | Primary developer language and system default |
| `de` | 🇩🇪 **German** | `100% Manually Verified` | Core market and compliance baseline |
| `fr` | 🇫🇷 **French** | `Auto-Generated` | Needs manual native review and polishing |
| `es` | 🇪🇸 **Spanish** | `Auto-Generated` | Needs manual native review and polishing |
| `pt-BR` | 🇧🇷 **Portuguese (Brasil)** | `Auto-Generated` | Needs manual native review and polishing |
| `ru` | 🇷🇺 **Russian** | `Auto-Generated` | Needs manual native review and polishing |
| `it` | 🇮🇹 **Italian** | `Auto-Generated` | Needs manual native review and polishing |
| `pl` | 🇵🇱 **Polish** | `Auto-Generated` | Needs manual native review and polishing |
| `tr` | 🇹🇷 **Turkish** | `Auto-Generated` | Needs manual native review and polishing |
| `nl` | 🇳🇱 **Dutch** | `Auto-Generated` | Needs manual native review and polishing |
| `ja` | 🇯🇵 **Japanese** | `Auto-Generated` | Needs manual native review and polishing |
| `ko` | 🇰🇷 **Korean** | `Auto-Generated` | Needs manual native review and polishing |
| `pt` | 🇵🇹 **European Portuguese** | `Auto-Generated` | Needs manual native review and polishing |
> [!WARNING]
> **Autogeneration Quality Rule**
> Any newly contributed languages must be committed as `"Auto-Generated"` until fully reviewed and signed off by a native speaker in a pull request.
---
## ⚖️ Strict Legal Exclusion Rule
Our legal pages have strict constraints to protect user privacy and avoid regulatory liabilities.
> [!IMPORTANT]
> **DO NOT TRANSLATE LEGAL DOCUMENTS**
> The legal notice ([impressum.html](file:///Users/koala/Documents/KoalaPlay/website/impressum.html)) and privacy policy ([datenschutz.html](file:///Users/koala/Documents/KoalaPlay/website/datenschutz.html)) **MUST remain exclusively in English and German**.
>
> * **Rationale:** Legal compliance under the European Union General Data Protection Regulation (GDPR) and the German Digital Services Act (DDG). Offering automated translations of legally binding notices introduces compliance risks due to potential mistranslations of liability limits.
> * **Technical Fallback:** The dynamic initializer script (`lang-init.js`) is configured to automatically fallback to **English** for legal pages if a user visits them with a French, Spanish, or other unsupported language preference, keeping their dynamic dropdown choice intact for homepage links.
---
## 🛠️ Step-by-Step: Adding a New Language
Adding a new language (e.g., Italian - `it`) is straightforward. Follow these four structured steps:
### Step 1: Create the Translation Dictionary
Create a new JSON file inside the locales directory named `[lang].json` (e.g., `website/locales/it.json`).
1. Copy the structure of [`website/locales/en.json`](file:///Users/koala/Documents/KoalaPlay/website/locales/en.json) to use as your baseline.
2. Translate all string values while keeping the JSON keys identical.
3. Configure the language metadata keys at the top of the file:
```json
{
"LANG_CODE": "it",
"HTML_CLASS": "lang-it",
"CANONICAL_PATH": "it/",
"LANG_TOGGLE_URL": "../",
"LANG_TOGGLE_TEXT": "EN"
}
```
### Step 2: Register in the Compiler
Open the static site generator script [`website/build.js`](file:///Users/koala/Documents/KoalaPlay/website/build.js) and append your new language code to the active `languages` array:
```javascript
// Add 'it' to the array
const languages = ['en', 'de', 'fr', 'es', 'pt-BR', 'ru', 'it'];
```
### Step 3: Run the Build Script
Execute the compiler from the root of the repository:
```bash
node website/build.js
```
The compiler will automatically:
1. Load the new JSON translation file.
2. Create the target subdirectory `/website/www/it/`.
3. Generate the compiled `/website/www/it/index.html` landing page, injecting correct relative assets and canonical metadata.
### Step 4: Update the Dashboard
Open this `TRANSLATION.md` file and add your language to the **Supported Languages Dashboard** table, marking it as `Auto-Generated` (unless manually verified).
---
## 🔮 Future Roadmap: Dynamic Utility Pages
For pages that require fully dynamic, client-side interactions (like the room invitation bridge [`join.html`](file:///Users/koala/Documents/KoalaPlay/website/join.html)), we need to scale to unlimited languages without bloating the HTML size or polluting the URL.
### Clean Client-Side i18n Architecture
To maintain zero URL pollution (e.g. keeping invitation links clean as `/join.html#join:roomID:password`), we propose an **asynchronous JSON dictionary injection architecture**:
#### 1. Page Lifecycle Flow
1. **User Landing:** The guest enters `/join.html` with a shared hash.
2. **Language Resolution:** `lang-init.js` immediately reads their saved preference (`localStorage` or `navigator.language`) and applies the active class (e.g. `html.lang = "es"`).
3. **Async Fetching:** A client-side loader script (`i18n-client.js`) runs asynchronously, downloading the correct dictionary (`fetch("/locales/es.json")`).
4. **DOM Translation:** The script scans the page for elements carrying a `data-i18n` attribute and safely updates their text content at runtime, avoiding dual-text nodes and stylesheet recalculations.
#### 2. Declarative HTML Markup
Elements are defined with custom data attributes specifying translation keys. English text is placed as the static HTML fallback:
```html
<h1 data-i18n="JOIN_TITLE">Ready to sync?</h1>
<p id="join-desc" data-i18n="JOIN_SUBTITLE">You've been invited to join a session.</p>
```
#### 3. Zero-Dependency Engine (`i18n-client.js`)
```javascript
document.addEventListener('DOMContentLoaded', async () => {
// 1. Recover the language determined during early initialization
const activeLang = document.documentElement.lang || 'en';
if (activeLang === 'en') return; // Default markup is already in English
// 2. Fetch the corresponding locale JSON asynchronously
try {
const response = await fetch(`locales/${activeLang}.json`);
if (!response.ok) throw new Error('Locale file unavailable');
const dictionary = await response.json();
// 3. Scan and translate data-i18n attributes
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (dictionary[key]) {
if (el.tagName === 'IMG') {
el.alt = dictionary[key];
} else {
el.textContent = dictionary[key];
}
}
});
} catch (err) {
console.warn('Dynamic i18n loading failed. Defaulting to English:', err);
}
});
```
#### Core Benefits
* **Zero URL Pollution:** Keeps invitation hashes private and avoids messy query parameters (`?lang=de`), protecting user privacy.
* **Optimal Performance:** Eliminates duplicate hidden text blocks, cutting page weight in half and ensuring smooth rendering.
* **Infinite Scale:** Adding new languages to dynamic pages requires zero edits to HTML markup; the engine simply fetches new JSON dictionaries on-demand.
---
## 🔌 Extension Internationalization (i18n)
In **v2.0**, we extended full internationalization support to the **Browser Extension itself**. The architecture mirrors our web-based dynamic localization model to maintain complete parity.
* **Locales Directory:** [`extension/locales/`](file:///Users/koala/Documents/KoalaPlay/extension/locales/)
* **Active Dictionaries:**
* [`en.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/en.json) (🇬🇧 Baseline English)
* [`de.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/de.json) (🇩🇪 German)
* [`fr.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/fr.json) (🇫🇷 French)
* [`es.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/es.json) (🇪🇸 Spanish)
* [`pt-BR.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/pt-BR.json) (🇧🇷 Portuguese (Brasil))
* [`ru.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/ru.json) (🇷🇺 Russian)
* [`it.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/it.json) (🇮🇹 Italian)
* [`pl.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/pl.json) (🇵🇱 Polish)
* [`tr.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/tr.json) (🇹🇷 Turkish)
* [`nl.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/nl.json) (🇳🇱 Dutch)
* [`ja.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/ja.json) (🇯🇵 Japanese)
* [`ko.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/ko.json) (🇰🇷 Korean)
* [`pt.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/pt.json) (🇵🇹 European Portuguese)
* **Translation Engine:** [`extension/i18n.js`](file:///Users/koala/Documents/KoalaPlay/extension/i18n.js)
* **Validation Script:** [`scripts/test-locales.js`](file:///Users/koala/Documents/KoalaPlay/scripts/test-locales.js)
### ⚙️ How it Works inside the Extension
1. **System Locale Auto-Detection**: On first run, the extension detects the browser system language using `navigator.language` or `chrome.i18n.getUILanguage()`.
2. **On-the-Fly Redraws**: When the user selects a different language in the settings tab (`#langSelector`), the selection is stored in `chrome.storage.sync` and the translation engine immediately triggers `translateDOM()`. The interface, empty state cards, tooltips, dynamic onboarding tutorial guides, and status badges re-render instantly without reloading the popup.
3. **Localized System Notifications**: On play, pause, or seek commands, `background.js` retrieves the user's active locale preference from storage, loads the correct dictionary, and pushes native OS notifications fully translated.
### 🧪 Auditing & Sync Checks
To ensure that no language dictionary falls out of sync (causing missing labels or blank interfaces), developers must run the locale auditor tool before packaging releases:
```bash
node scripts/test-locales.js
```
This script asserts that all JSON dictionary files under `extension/locales/` share exactly the same set of keys as the English baseline (`en.json`).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

+1 -1
View File
@@ -102,7 +102,7 @@
"priceCurrency": "EUR"
},
"description": "Watch Netflix, YouTube, Twitch, Jellyfin, Emby and almost any HTML5 video in perfect sync with friends. Open source, privacy-first, free.",
"softwareVersion": "2.2.4",
"softwareVersion": "2.3.0",
"license": "https://opensource.org/licenses/MIT",
"sameAs": "https://github.com/Shik3i/KoalaSync",
"image": "https://sync.koalastuff.net/assets/NewLogoIcon.webp",
+2 -2
View File
@@ -1,4 +1,4 @@
{
"version": "2.2.4",
"date": "2026-06-09T23:42:27Z"
"version": "2.3.0",
"date": "2026-06-14T03:17:45Z"
}