Peer usernames are remote-controlled and were interpolated into an innerHTML string in updateLobbyUI. The server only truncates them to 30 chars, so a peer could inject markup into everyone else's popup: enough to load a remote image (leaking viewer IPs) or spoof readiness badges. Inline handlers were already blocked by the MV3 default CSP. Build the peer items with the DOM API, matching the pattern the sibling peer list already uses. Add the two checks that would have caught this before upload: - eslint no-unsanitized, which reproduces the AMO warning at lint time - addons-linter on the built XPI in verify-release, with --warnings-as-errors since it exits 0 on warnings and AMO rejects them Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
KoalaSync Website & Invitation Bridge
This directory contains the static KoalaSync website. It is both the public marketing/onboarding site and the browser-side invitation bridge for joining sync rooms.
Where You Are
template.html,style.css,styles/*.css,app.js,lang-init.js, andlocales/*.jsonare source files.style.cssis the local-development manifest; production concatenates the modules in the order declared bywebsite/build.cjs.build.cjscompiles the static site intowebsite/www/.website/www/is generated output. Do not edit files there directly.join.htmlhandles room-invite links and communicates with the extension throughbridge.js.llms.txtgives AI tools a detailed product profile and is linked from every localized landing-page<head>.alternatives/contains comparison pages for users evaluating KoalaSync against other watch-party approaches.
Core Roles
1. Marketing & Onboarding
The site explains KoalaSync, links to browser stores, documents self-hosting, and provides localized user-facing copy in 15 languages:
en, de, fr, es, it, nl, pl, pt, pt-BR, tr, ru, ja, ko, zh, and uk.
2. Invitation Bridge (join.html)
When a user opens an invitation such as https://sync.koalastuff.net/join.html#join:roomID:pass, the page:
- detects whether the KoalaSync extension is installed via the extension's
bridge.jscontent script, - keeps room credentials inside the URL hash so they are not sent to the web server,
- asks the extension to join the target room automatically when possible.
Architecture
The website is 100% static HTML, CSS, and JavaScript.
- Static i18n compiler:
build.cjscombinestemplate.htmlwith dictionaries inlocales/. - Build-time minification: Source CSS/JS stays readable; generated output uses
.min.cssand.min.js. - Strangler CSS architecture:
styles/*.csssupplies page-specific production bundles;style.legacy.cssis the byte-identical, unloaded reference monolith. The landing bundle preserves the legacy cascade while excluding Join- and Alternatives-only rules. Structural CSS is not activated after paint because that causes layout shifts. - Zero backend: The compiled site can be hosted by any static file server.
- Zero external assets: Fonts, icons, scripts, and images must remain self-hosted.
- Generated SEO/runtime files:
version.json,robots.txt,llms.txt, clean URLs, localized pages, and minified assets are copied intowww/;sitemap.xmlis generated there from the current route set.
Local Development & Compilation
From the repository root:
node website/build.cjs
npx serve -l 5000 website/www
Then open:
http://localhost:5000/for the default page.http://localhost:5000/de/for a localized page.http://localhost:5000/join.html#join:test-room:test-passto test the invitation bridge path.
Focused verification:
node scripts/test-website-locales.mjs
node scripts/test-website-theme.mjs
node --check website/app.js
node --check website/lang-init.js
Full verification:
npm run verify
Hosting with Caddy
Caddy is the recommended production server because it handles HTTPS and static file serving cleanly. For a complete website plus relay reverse-proxy setup, use the root Caddyfile.example.
Minimal static-site block:
sync.koalastuff.net {
root * /var/www/koalasync/website/www
try_files {path} {path}.html {path}/
file_server
encode zstd gzip
# Themed 404 page (Caddy returns an empty 404 without this)
handle_errors {
@notfound expression {err.status_code} == 404
rewrite @notfound /404.html
file_server
}
@static {
file
path *.ico *.css *.js *.png *.svg *.webp *.avif
}
header @static Cache-Control "public, max-age=31536000, must-revalidate"
header {
Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
}
}
Do Not Break
- Do not edit
website/www/manually; rebuild it from sources. - Do not add external CDNs, fonts, analytics, or third-party scripts.
- Keep invite credentials in the URL hash, not query parameters.
- Keep locale files synchronized with
website/build.cjsandscripts/test-website-locales.mjs. website/www/is gitignored build output. Commit the source changes only; rebuildwww/locally or in CI for verification and deployment.