diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5352bdd..bda4da8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -137,6 +137,12 @@ jobs: npm ci npm run build:extension + - name: Generate extension checksums + run: | + cd dist + sha256sum koalasync-chrome.zip koalasync-firefox.zip > SHA256SUMS + cat SHA256SUMS + - name: Generate artifact attestation for extensions uses: actions/attest@v4 with: @@ -158,6 +164,7 @@ jobs: files: | dist/koalasync-chrome.zip dist/koalasync-firefox.zip + dist/SHA256SUMS name: Release ${{ github.ref_name }} generate_release_notes: true draft: false diff --git a/README.md b/README.md index 6c1b38f..6346d24 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ The easiest and safest way to install KoalaSync is directly through the official Microsoft Edge Add-on

-*(For manual offline installation: Download the latest `.zip` from the [Releases](https://github.com/Shik3i/KoalaSync/releases) page and load it as an "Unpacked Extension" in Developer Mode).* +*(For manual offline installation: Download the latest `.zip` from the [Releases](https://github.com/Shik3i/KoalaSync/releases) page, extract it, and load the extracted browser package using your browser's developer or manual extension installation process.)* **How to use:** 1. **Create a Room:** Click the Koala icon in your browser and hit `+ Create New Room`. @@ -112,7 +112,14 @@ To verify your relay is reachable from outside, visit `https://your-domain.com` #### Supply Chain Security (v2.2.2+) -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. +Extension ZIPs attached to GitHub Releases and Docker images published to GitHub Container Registry receive signed [artifact attestations](https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations). These attestations connect an artifact's exact digest to this repository, its release tag and commit, and the public GitHub Actions workflow that built it. + +Each new GitHub Release also includes `SHA256SUMS` for a quick checksum comparison: +```bash +sha256sum --check SHA256SUMS +``` + +Checksums detect a changed or incomplete download. Signed provenance additionally verifies where the artifact came from and which workflow built it. Neither check guarantees that the software contains no bugs or vulnerabilities. These attestations cover the GitHub release ZIPs and GHCR images; they do not by themselves prove that browser-store packages are byte-for-byte identical. **Verify a Docker image:** ```bash diff --git a/docs/devops.md b/docs/devops.md index 4108f2c..2b249b2 100644 --- a/docs/devops.md +++ b/docs/devops.md @@ -24,7 +24,7 @@ When you push a Git tag matching `v*` (e.g., `v2.5.1`), the GitHub Actions relea - `README.md` (updates badge and announcement banner) - `website/sitemap.xml` (updates `lastmod` dates) 3. **Commits and pushes** these version updates back to the `main` branch automatically with the commit message `chore(release): update versions to vX.X.X [skip ci]`. -4. **Builds the extension** for both Chrome and Firefox and publishes the zipped archives. +4. **Builds the extension** for both Chrome and Firefox and publishes the zipped archives with a `SHA256SUMS` checksum file and signed provenance attestations. 5. **Builds the website** and uploads website artifacts. 6. **Builds and publishes** the Docker image for the relay server to the GitHub Container Registry (`ghcr.io`). diff --git a/examples/Caddyfile.example b/examples/Caddyfile.example index e74721e..ae3bed2 100644 --- a/examples/Caddyfile.example +++ b/examples/Caddyfile.example @@ -15,6 +15,10 @@ # root * /var/www/koalasync/website/www # encode zstd gzip # +# # Keep one canonical URL for indexable support pages. +# redir /help.html /help 308 +# redir /site-access-help.html /site-access-help 308 +# # # Clean URLs support (resolves /join to join.html, etc.) # try_files {path} {path}.html {path}/ # file_server @@ -61,6 +65,10 @@ sync.koalastuff.net { encode zstd gzip root * /var/www/koalasync/website/www + # Keep one canonical URL for indexable support pages. + redir /help.html /help 308 + redir /site-access-help.html /site-access-help 308 + # Clean URLs: Resolves paths without .html in the URL try_files {path} {path}.html {path}/ file_server @@ -96,4 +104,3 @@ syncserver.koalastuff.net { encode zstd gzip reverse_proxy KoalaSync:3000 } - diff --git a/scripts/test-website-theme.mjs b/scripts/test-website-theme.mjs index 89d3e4f..62aa059 100644 --- a/scripts/test-website-theme.mjs +++ b/scripts/test-website-theme.mjs @@ -102,6 +102,7 @@ if (!llmsText.includes(`Current website release: ${websiteVersion}`)) { throw new Error(`llms.txt release must match website/version.json (${websiteVersion})`); } const requiredSupportSocialMetadata = [ + '', 'name="twitter:card" content="summary_large_image"', 'name="twitter:title"', 'name="twitter:description"', @@ -132,12 +133,12 @@ for (const metadata of requiredHelpMetadata) { } } if (!llmsText.includes('[Help Center](https://sync.koalastuff.net/help)') - || !llmsText.includes('[Website access guide](https://sync.koalastuff.net/site-access-help.html)')) { + || !llmsText.includes('[Website access guide](https://sync.koalastuff.net/site-access-help)')) { throw new Error('llms.txt must expose the Help Center and website-access guide'); } if (!websiteBuild.includes("['log', '-1', '--format=%cs', '--', ...sourceFiles]") - || !websiteBuild.includes("lastmod(['website/help.html', 'website/styles/support.css'])") - || !websiteBuild.includes("lastmod(['website/site-access-help.html'])") + || !websiteBuild.includes("lastmod(['website/help.html', 'website/styles/support.css', 'website/app.js'])") + || !websiteBuild.includes("lastmod(['website/site-access-help.html', 'website/app.js'])") || !websiteBuild.includes("['rev-parse', '--is-shallow-repository']") || !websiteBuild.includes("['diff', '--name-only', '--']") || !websiteBuild.includes("['diff', '--cached', '--name-only', '--']") @@ -145,6 +146,9 @@ if (!websiteBuild.includes("['log', '-1', '--format=%cs', '--', ...sourceFiles]" || !websiteBuild.includes('sourceFiles.some(file => dirtySourceFiles.has(file))')) { throw new Error('Sitemap lastmod values must come from the mapped source files in Git'); } +if (websiteBuild.includes('') || websiteBuild.includes('')) { + throw new Error('Sitemap must not emit ignored changefreq or priority hints'); +} const documentedRelayUrls = [...llmsText.matchAll(/`(wss:\/\/[^`\s]+)`/g)].map(([, value]) => new URL(value)); const hasCanonicalPublicRelay = documentedRelayUrls.some((url) => ( url.protocol === 'wss:' && @@ -179,6 +183,10 @@ for (const value of requiredSelfHostingValues) { if ((selfHostingExamples.match(/localhost:3000/g) || []).length !== 2 || selfHostingExamples.includes('KoalaSync:3000')) { throw new Error('Both Caddy examples must match the compose loopback port binding'); } +if ((selfHostingExamples.match(/\/help\.html \/help 308/g) || []).length !== 2 + || (selfHostingExamples.match(/\/site-access-help\.html \/site-access-help 308/g) || []).length !== 2) { + throw new Error('Both Caddy examples must redirect legacy support-page URLs to their clean canonicals'); +} const landingStylesheet = landingStylesheets[0][0]; if (!/href="\{\{ASSET_PATH\}\}landing\.min\.css"/.test(landingStylesheet)) { diff --git a/website/README.md b/website/README.md index fe1bc69..fb84af1 100644 --- a/website/README.md +++ b/website/README.md @@ -73,6 +73,8 @@ Minimal static-site block: ```caddy sync.koalastuff.net { root * /var/www/koalasync/website/www + redir /help.html /help 308 + redir /site-access-help.html /site-access-help 308 try_files {path} {path}.html {path}/ file_server encode zstd gzip diff --git a/website/app.js b/website/app.js index 6fc1ddf..dd7bd6b 100644 --- a/website/app.js +++ b/website/app.js @@ -235,20 +235,55 @@ document.addEventListener('DOMContentLoaded', () => { // Auto-update URL hash as user scrolls through sections // (preserves position across language switches) - if ('IntersectionObserver' in window) { - const sectionObserver = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - if (entry.target.id === 'top') { - history.replaceState(null, null, window.location.pathname + window.location.search); - } else { - history.replaceState(null, null, '#' + entry.target.id); - } - if (forestGreet) forestGreet(); - } + const trackedSections = [...document.querySelectorAll('section[id], header[id]')]; + if (trackedSections.length > 0) { + let hashUpdateFrame = null; + let hashNavigationTimer = null; + let hashNavigationInProgress = false; + const updateSectionHash = () => { + hashUpdateFrame = null; + const readingLine = 112; + const activeSection = trackedSections.reduce((closest, section) => { + const distance = Math.abs(section.getBoundingClientRect().top - readingLine); + const closestDistance = Math.abs(closest.getBoundingClientRect().top - readingLine); + return distance < closestDistance ? section : closest; }); - }, { threshold: 0.3 }); - document.querySelectorAll('section[id], header[id]').forEach(el => sectionObserver.observe(el)); + + const nextHash = activeSection.id === 'top' ? '' : '#' + activeSection.id; + if (window.location.hash === nextHash) return; + if (nextHash === '') { + history.replaceState(null, null, window.location.pathname + window.location.search); + } else { + history.replaceState(null, null, nextHash); + } + if (forestGreet) forestGreet(); + }; + const scheduleSectionHashUpdate = () => { + if (hashUpdateFrame !== null) return; + hashUpdateFrame = requestAnimationFrame(updateSectionHash); + }; + const finishHashNavigation = () => { + hashNavigationTimer = null; + hashNavigationInProgress = false; + scheduleSectionHashUpdate(); + }; + const beginHashNavigation = () => { + hashNavigationInProgress = true; + if (hashNavigationTimer !== null) clearTimeout(hashNavigationTimer); + hashNavigationTimer = setTimeout(finishHashNavigation, 250); + }; + const handleSectionScroll = () => { + if (!hashNavigationInProgress) { + scheduleSectionHashUpdate(); + return; + } + if (hashNavigationTimer !== null) clearTimeout(hashNavigationTimer); + hashNavigationTimer = setTimeout(finishHashNavigation, 180); + }; + window.addEventListener('hashchange', beginHashNavigation); + window.addEventListener('scroll', handleSectionScroll, { passive: true }); + if (window.location.hash) beginHashNavigation(); + else scheduleSectionHashUpdate(); } // Navbar scroll effect (class-based so it follows the active theme) diff --git a/website/build.cjs b/website/build.cjs index 1f4ee77..8b89cc9 100644 --- a/website/build.cjs +++ b/website/build.cjs @@ -109,12 +109,22 @@ function stageFlagFontSubset(websiteDir, wwwDir) { function copyDirSync(src, dest) { fs.mkdirSync(dest, { recursive: true }); for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + if (entry.name === '.DS_Store') continue; const s = path.join(src, entry.name); const d = path.join(dest, entry.name); entry.isDirectory() ? copyDirSync(s, d) : fs.copyFileSync(s, d); } } +function removeBuildMetadata(dir) { + if (!fs.existsSync(dir)) return; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const entryPath = path.join(dir, entry.name); + if (entry.name === '.DS_Store') fs.rmSync(entryPath, { force: true }); + else if (entry.isDirectory()) removeBuildMetadata(entryPath); + } +} + function sha8(buf) { return crypto.createHash('sha256').update(buf).digest('hex').slice(0, 8); } function sha384(buf) { return 'sha384-' + crypto.createHash('sha384').update(buf).digest('base64'); } @@ -164,6 +174,7 @@ async function compile() { const websiteDir = __dirname; const wwwDir = path.join(websiteDir, 'www'); fs.mkdirSync(wwwDir, { recursive: true }); + removeBuildMetadata(wwwDir); // ── 0. Auto-generate website logo sizes and sync favicons ── console.log('Generating responsive website logos...'); @@ -796,34 +807,24 @@ function generateSitemap(websiteDir, wwwDir) { xml += ` https://sync.koalastuff.net/privacy${lastmod(['website/privacy.html'])} - monthly - 0.3 - https://sync.koalastuff.net/help${lastmod(['website/help.html', 'website/styles/support.css'])} - weekly - 0.8 + https://sync.koalastuff.net/help${lastmod(['website/help.html', 'website/styles/support.css', 'website/app.js'])} - https://sync.koalastuff.net/site-access-help.html${lastmod(['website/site-access-help.html'])} - weekly - 0.8 + https://sync.koalastuff.net/site-access-help${lastmod(['website/site-access-help.html', 'website/app.js'])} https://sync.koalastuff.net/de/datenschutz${lastmod(['website/datenschutz-de.html'])} - monthly - 0.3 `; - function addPage(relativePath, changefreq, priority, templateSource) { + function addPage(relativePath, templateSource) { for (const lang of languages) { const loc = `https://sync.koalastuff.net/${lang.prefix}${relativePath}`; const sourceFiles = [templateSource, `website/locales/${lang.code}.json`]; xml += ` - ${loc}${lastmod(sourceFiles)} - ${changefreq} - ${priority}`; + ${loc}${lastmod(sourceFiles)}`; for (const alt of languages) { const altHref = `https://sync.koalastuff.net/${alt.prefix}${relativePath}`; xml += ` @@ -836,8 +837,8 @@ function generateSitemap(websiteDir, wwwDir) { } } - addPage('', 'weekly', '1.0', 'website/template.html'); - addPage('alternatives', 'weekly', '0.7', 'website/alternatives/index.html'); + addPage('', 'website/template.html'); + addPage('alternatives', 'website/alternatives/index.html'); const subpages = [ ['alternatives/teleparty', 'website/alternatives/teleparty.html'], @@ -848,7 +849,7 @@ function generateSitemap(websiteDir, wwwDir) { ['alternatives/twoseven', 'website/alternatives/twoseven.html'] ]; for (const [sub, templateSource] of subpages) { - addPage(sub, 'weekly', '0.7', templateSource); + addPage(sub, templateSource); } xml += `\n\n`; diff --git a/website/help.html b/website/help.html index 4dec076..e444e10 100644 --- a/website/help.html +++ b/website/help.html @@ -3,8 +3,8 @@ - KoalaSync Help | Setup, Video Detection & Troubleshooting - + KoalaSync Help | Setup, Chat, Privacy & Troubleshooting + @@ -16,7 +16,7 @@ - + @@ -25,7 +25,7 @@ - + @@ -51,8 +51,8 @@ "@type": ["CollectionPage", "FAQPage"], "@id": "https://sync.koalastuff.net/help#webpage", "name": "KoalaSync Help", - "headline": "KoalaSync Help: Setup, Video Detection and Troubleshooting", - "description": "Help for granting browser access, finding missing video tabs, diagnosing video detection, fixing playback synchronization, and reporting reproducible bugs.", + "headline": "KoalaSync Help: Setup, Chat, Privacy and Troubleshooting", + "description": "Help for joining rooms, granting browser access, fixing encrypted room chat and playback synchronization, understanding data handling, and verifying official releases.", "url": "https://sync.koalastuff.net/help", "datePublished": "2026-07-28", "dateModified": "2026-07-28", @@ -68,6 +68,14 @@ "text": "KoalaSync works with video players across many websites. It uses access on the video tab you actively select to find and control the video element, synchronize playback, and display optional room chat. Open tab titles populate the tab selector; KoalaSync does not create a general browsing or watch history." } }, + { + "@type": "Question", + "name": "Why does a KoalaSync invite link not join the room?", + "acceptedAnswer": { + "@type": "Answer", + "text": "Open the complete invite link in the same desktop browser profile where KoalaSync is installed. If the invite page was already open when the extension was installed or enabled, reload it. Confirm that the browser allows KoalaSync to run on sync.koalastuff.net, then ask the sender for a newly copied invite if the link is incomplete." + } + }, { "@type": "Question", "name": "Why does a video tab not appear in KoalaSync?", @@ -84,6 +92,14 @@ "text": "Select the tab containing the visible player and start playback once. Check Video Debug Info in the Status tab, reload after permission changes, and temporarily disable extensions that modify the player. Some protected or non-HTML5 players may need site-specific support." } }, + { + "@type": "Question", + "name": "Why does KoalaSync room chat not work for everyone?", + "acceptedAnswer": { + "@type": "Answer", + "text": "Room chat requires KoalaSync 3.0.0 or newer. The room creator receives a chat encryption secret when creating a new room, and every friend must join through that room's current invite link to receive the same secret. Manual room entry and old invite links can still join playback synchronization, but do not provide chat access." + } + }, { "@type": "Question", "name": "Why are connected KoalaSync participants not synchronized?", @@ -91,6 +107,38 @@ "@type": "Answer", "text": "Confirm that everyone uses the same KoalaSync version and selected the correct local video tab. Reload after website-access changes, test play, pause, and seeking separately, and collect a debug report from each affected participant." } + }, + { + "@type": "Question", + "name": "Does KoalaSync stream the video to other participants?", + "acceptedAnswer": { + "@type": "Answer", + "text": "No. KoalaSync does not stream, upload, proxy, share, or bypass access to video content. Everyone watches locally in their own browser and needs their own access to the website or streaming service. KoalaSync only synchronizes playback actions such as play, pause, and seeking." + } + }, + { + "@type": "Question", + "name": "Which devices and browsers support KoalaSync?", + "acceptedAnswer": { + "@type": "Answer", + "text": "KoalaSync is a desktop browser extension for Chrome, Firefox, Edge, and compatible Chromium-based browsers on Windows, macOS, Linux, and ChromeOS. It does not currently provide a mobile app or Safari extension. Website compatibility also depends on the site's video player." + } + }, + { + "@type": "Question", + "name": "What data does KoalaSync send from the browser?", + "acceptedAnswer": { + "@type": "Answer", + "text": "While you are in a room, KoalaSync sends the room, peer, and playback data needed for live synchronization. Display names and, depending on privacy settings, tab or media titles may be shared with room participants. Chat text is encrypted in the extension before transmission. KoalaSync does not create a persistent chat, browsing, synchronization, or watch history." + } + }, + { + "@type": "Question", + "name": "How can official KoalaSync releases be verified?", + "acceptedAnswer": { + "@type": "Answer", + "text": "The extension, relay server, build scripts, release workflow, and Dockerfile are public under the MIT License. Extension ZIPs on GitHub Releases and Docker images on GitHub Container Registry receive signed provenance attestations that bind their exact digest to this repository, a release tag and commit, and the public GitHub Actions workflow." + } } ] }, @@ -147,50 +195,76 @@
Help center -

How can we help?

-

Start with the symptom you see. These checks cover browser access, missing tabs, video detection, playback synchronization, and the information needed for a useful bug report.

+

KoalaSync Help & Troubleshooting

+

Start with the problem or question you have. These guides cover invites, browser access, video detection, encrypted chat, playback synchronization, supported devices, privacy, and release verification.

-
-

Choose your problem

-
- - - Browser access -

KoalaSync cannot access the video site

-

Restore β€œread and change site data” access in Chrome, Edge, Brave, Opera, or Vivaldi.

- Open access guide β†’ -
- - - Tab list -

The video tab is missing

-

Test the clutter filter, reload the page, and identify domains that KoalaSync hides by mistake.

- Check missing tabs β†’ -
- - - Video detection -

The tab appears, but no video is found

-

Check the selected tab, player state, Shadow DOM diagnostics, and conflicting extensions.

- Diagnose the player β†’ -
- - - Synchronization -

Play, pause, or seeking does not sync

-

Compare versions, selected tabs, room state, and debug reports from every participant.

- Check synchronization β†’ -
+
+

Choose a help topic

+
+ + + +
-
- Privacy & permissions -

Why does the browser show such a broad warning?

-

Browsers may say that KoalaSync can β€œread and change all your data on all websites.” The wording is broad because KoalaSync is designed to work with video players across many different websites instead of a short fixed list.

-

KoalaSync injects its synchronization script into the video tab you actively select. On that tab it needs to find and control the video element, synchronize play, pause, and seeking, and display the optional room chat overlay. Open tab titles are read to populate the tab selector. KoalaSync does not create a general browsing or watch history.

-

Read the complete permission and data-handling explanation

+
+ Invite links +

The invite opens, but nothing happens

+
    +
  1. Open the invite in the same desktop browser profile where KoalaSync is installed. Extensions installed in another browser or profile cannot receive the join request.
  2. +
  3. If you installed, enabled, or updated KoalaSync after opening the invite, reload the invite page once so the extension can connect to it.
  4. +
  5. Confirm that KoalaSync is allowed to run on sync.koalastuff.net. If the browser blocks access, follow the site-access guide, then reload the invite.
  6. +
  7. Ask the sender to open KoalaSync and copy the invite link again. Use the complete link without removing anything after #; that part contains the room credentials and current chat secret.
  8. +
  9. Wait for the join page to report success. Joining the room does not select a video automatically, so open KoalaSync afterward and choose your local video tab under Sync.
  10. +
+
+ +
+ The extension must be detected on the invite page. +

If the page still offers an install button even though KoalaSync is installed, the usual causes are a different browser profile, blocked website access, or a page that was opened before the extension became available.

+
+
@@ -201,7 +275,7 @@
  • Close and reopen the KoalaSync popup, then check Select Video again.
  • Open KoalaSync Settings and turn off Hide Clutter Tabs.
  • Return to the tab selector. If the tab now appears, the site was filtered as clutter by mistake.
  • -
  • If the tab is still missing, verify website access using the site-access guide.
  • +
  • If the tab is still missing, verify website access using the site-access guide.
  • @@ -225,6 +299,26 @@
    +
    + Room chat +

    Chat does not work, or one person cannot see it

    +

    Room chat was introduced in KoalaSync 3.0.0. Every participant who wants to send or read chat messages needs version 3.0.0 or newer. Older versions can still join a room and synchronize playback, but they do not support room chat.

    +
      +
    1. Update KoalaSync in every browser. Open the extension and check the version in the top-right corner. Each participant needs version 3.0.0 or newer.
    2. +
    3. After updating, the room creator should leave the old room and click Create New Room. This creates a new chat encryption secret and includes it in the new invite link.
    4. +
    5. Send that newly copied invite link to your friends. Each friend must leave the old room and join through the link so their extension receives the same chat encryption secret.
    6. +
    7. Do not join by typing only the room ID and password, and do not reuse an old invite link beginning with #join:. Both can connect playback synchronization, but neither contains the chat secret.
    8. +
    9. Each participant must select their local video tab, then open Settings β†’ Room Chat and turn on Enable Room Chat. The chat appears on the selected video page, not inside the extension popup.
    10. +
    +
    + +
    + Chat is live only. +

    There is no server-side message history. Someone who joins later sees only messages sent after they joined. Keep the invite link private: it contains the chat secret used to decrypt messages in that room.

    +
    +
    +
    +
    Synchronization

    Everyone is connected, but playback does not sync

    @@ -237,6 +331,84 @@
    +
    + Video access +

    Does KoalaSync send the video to my friends?

    +

    No. KoalaSync does not stream, upload, proxy, download, or redistribute video content. It only sends the live room and playback events needed to keep actions such as play, pause, and seeking synchronized.

    +
      +
    • Everyone watches the video locally in their own browser on the original website.
    • +
    • Each participant needs their own valid access to the website or streaming service.
    • +
    • KoalaSync does not bypass subscriptions, logins, DRM, regional restrictions, or platform rules.
    • +
    • Subtitles, audio tracks, fullscreen, and video quality remain local choices when the video player supports them.
    • +
    +
    + +
    + Compatibility +

    Which devices and browsers are supported?

    +

    KoalaSync is currently a desktop browser extension. It supports Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave, Vivaldi, and other compatible Chromium-based desktop browsers on Windows, macOS, Linux, and ChromeOS.

    +
      +
    • There is currently no KoalaSync mobile app for phones or tablets.
    • +
    • There is currently no Safari extension.
    • +
    • Website compatibility depends on the browser and the site's video player. Websites can change their players, DRM behavior, or browser restrictions at any time.
    • +
    • Protected, customized, or non-HTML5 players may need site-specific support even in a supported browser.
    • +
    +
    + +
    + Privacy & permissions +

    Why does the browser show such a broad warning?

    +

    Browsers may say that KoalaSync can β€œread and change all your data on all websites.” The wording is broad because KoalaSync is designed to work with video players across many different websites instead of a short fixed list.

    +

    KoalaSync injects its synchronization script into the video tab you actively select. On that tab it needs to find and control the video element, synchronize play, pause, and seeking, and display the optional room chat overlay. Open tab titles are read to populate the tab selector. KoalaSync does not create a general browsing or watch history.

    +

    Read the complete permission and data-handling explanation

    +
    + +
    + Privacy +

    What data leaves my browser?

    +

    KoalaSync only connects to a relay while you are actively in a room. During that session, the relay temporarily processes the data required to connect participants and synchronize playback.

    +
      +
    • Room and presence: room ID, peer ID, display name, connection state, and the information needed to join the room.
    • +
    • Playback synchronization: actions and state such as play, pause, seeking, and the current playback position.
    • +
    • Titles: tab or media titles may be shared with room participants depending on your title privacy settings. They can be limited or disabled.
    • +
    • Room chat: message text is encrypted in the extension before transmission. The relay receives ciphertext and delivery metadata, but not the chat secret or readable message text.
    • +
    • Stored locally: preferences, current room credentials, and the chat secret are stored in the browser. The chat secret is not sent to the relay. Decrypted chat entries remain only in the current page overlay and are not written to browser storage.
    • +
    • Operational logs: the relay may log technical connection and security information such as IP addresses, protocol versions, socket or peer identifiers, and shortened room identifiers. Retention depends on the server infrastructure and configuration.
    • +
    +

    KoalaSync does not create a persistent browsing, room, synchronization, chat, or watch history. Browser or operating-system notifications may retain notification text according to their own settings.

    +

    Read the complete privacy policy

    +
    + +
    + Trust & verification +

    Why should I trust KoalaSync?

    +

    Trust should not depend on promises alone. The KoalaSync browser extension, relay server, build scripts, release workflow, and Dockerfile used for official GitHub releases are public under the MIT License.

    +

    Official extension ZIPs on GitHub Releases and Docker images on GitHub Container Registry are built by the public GitHub Actions release workflow. Signed SLSA provenance attestations connect each artifact's exact digest to this repository, its release tag and commit, and the workflow that built it.

    +

    You can install KoalaSync from an official browser store, download the Chrome or Firefox ZIP from GitHub Releases, build the extension yourself, or run your own relay server. Manual ZIP installation uses your browser's developer or manual extension installation process.

    + +
    + Advanced: verify signed build provenance +

    Install the GitHub CLI, download the release ZIP, and run:

    +
    gh attestation verify koalasync-chrome.zip \
    +  --repo Shik3i/KoalaSync
    +

    To verify the current Docker image:

    +
    gh attestation verify oci://ghcr.io/shik3i/koalasync:latest \
    +  --repo Shik3i/KoalaSync
    +
    +
    + +
    + What an attestation proves, and what it does not. +

    An attestation proves the origin and build workflow of the GitHub release ZIP or GHCR image with that exact digest. It does not guarantee that software has no bugs or vulnerabilities. It also does not by itself prove that a package downloaded from a browser store is byte-for-byte identical to a GitHub release ZIP.

    +
    +
    +
    +
    Still not working?

    Create a useful GitHub issue

    diff --git a/website/llms.txt b/website/llms.txt index c79b799..5787559 100644 --- a/website/llms.txt +++ b/website/llms.txt @@ -79,7 +79,7 @@ Compatibility depends on each website's player implementation and can change whe ## Help and troubleshooting - [Help Center](https://sync.koalastuff.net/help): Browser permissions, missing tabs, video detection, synchronization checks and safe bug reports -- [Website access guide](https://sync.koalastuff.net/site-access-help.html): Restore access when Chrome, Edge, Brave, Opera or Vivaldi blocks KoalaSync on a video site +- [Website access guide](https://sync.koalastuff.net/site-access-help): Restore access when Chrome, Edge, Brave, Opera or Vivaldi blocks KoalaSync on a video site - If a video tab is missing, disable `Hide Clutter Tabs` temporarily. If the tab then appears, report the affected domain so the filter can be corrected. - If a listed tab has no detected video, start playback once and inspect `Status` β†’ `Video Debug Info`. - GitHub issues are public. Remove private page URLs, titles, room or peer identifiers, participant names, custom server URLs, credentials and session parameters before publishing logs. diff --git a/website/site-access-help.html b/website/site-access-help.html index 5409d57..0e18bf5 100644 --- a/website/site-access-help.html +++ b/website/site-access-help.html @@ -9,11 +9,11 @@ - - - + + + - + @@ -35,10 +35,10 @@ "datePublished": "2026-07-15T10:24:59Z", "dateModified": "2026-07-19T19:20:05Z", "inLanguage": "en", - "url": "https://sync.koalastuff.net/site-access-help.html", + "url": "https://sync.koalastuff.net/site-access-help", "mainEntityOfPage": { "@type": "WebPage", - "@id": "https://sync.koalastuff.net/site-access-help.html" + "@id": "https://sync.koalastuff.net/site-access-help" }, "author": { "@type": "Organization", diff --git a/website/styles/support.css b/website/styles/support.css index a396bfb..e84b8b0 100644 --- a/website/styles/support.css +++ b/website/styles/support.css @@ -79,8 +79,7 @@ } .support-content > .support-card + .support-card, -.support-grid + .support-card, -.support-card + .support-grid, +.support-card + .support-topic-grid, .support-callout + .support-card { margin-top: 1.25rem; } @@ -130,62 +129,109 @@ margin-top: 0.6rem; } -.support-grid { +.support-topic-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem; margin-top: 1.25rem; } +.support-topic-index { + margin-bottom: 2.75rem; +} + .support-section-title { - margin: 0 0 0.25rem; + margin: 0 0 0.4rem; color: var(--text); text-align: center; font-size: clamp(1.45rem, 3vw, 1.9rem); } -.support-card-link { +.support-topic-group { position: relative; + overflow: hidden; + padding: 0; + border-top-color: color-mix(in oklch, var(--accent) 55%, var(--card-border)); +} + +.support-topic-group::before { + content: ''; + position: absolute; + inset: 0 0 auto; + height: 2px; + background: linear-gradient(90deg, var(--accent), color-mix(in oklch, var(--accent) 12%, transparent) 72%, transparent); +} + +.support-topic-header { + min-height: 7.4rem; + padding: 1.55rem 1.6rem 1.25rem; +} + +.support-topic-header h3 { + margin: 0 0 0.45rem; + color: var(--text); + font-size: 1.2rem; + letter-spacing: -0.015em; +} + +.support-topic-header p { + max-width: 34rem; + margin: 0; + color: var(--text-muted); + font-size: 0.9rem; + line-height: 1.55; +} + +.support-topic-links { display: block; - color: inherit; + border-top: 1px solid var(--card-border); +} + +.support-topic-links a { + display: flex; + min-height: 3.85rem; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.9rem 1.6rem; + color: var(--text); + font-size: 0.94rem; + font-weight: 700; + line-height: 1.4; text-decoration: none; - transition: transform 180ms ease, border-color 180ms ease, box-shadow 180ms ease; + transition: background-color 160ms ease, color 160ms ease, padding-left 160ms ease; } -.support-card-link:hover, -.support-card-link:focus-visible { - transform: translateY(-3px); - border-color: color-mix(in oklch, var(--accent) 55%, transparent); - box-shadow: 0 24px 65px rgba(0, 0, 0, 0.2); +.support-topic-links a + a { + border-top: 1px solid color-mix(in oklch, var(--card-border) 78%, transparent); } -.support-card-link:focus-visible { - outline: 3px solid color-mix(in oklch, var(--accent) 45%, transparent); - outline-offset: 3px; -} - -.support-card-link .support-eyebrow { - margin-bottom: 1rem; -} - -.support-card-icon { - float: right; - display: grid; - place-items: center; - width: 2.8rem; - height: 2.8rem; - margin-left: 0.75rem; - border: 1px solid color-mix(in oklch, var(--accent) 25%, transparent); - border-radius: 14px; - background: color-mix(in oklch, var(--accent) 9%, var(--card)); - font-size: 1.3rem; -} - -.support-card-cta { - display: inline-block; - margin-top: 1rem; +.support-topic-links a::after { + content: 'β€Ί'; + flex: 0 0 auto; color: var(--accent); - font-weight: 800; + font-size: 1.45rem; + font-weight: 500; + line-height: 1; + transition: transform 160ms ease; +} + +.support-topic-links a:hover { + padding-left: 1.75rem; + background: color-mix(in oklch, var(--accent) 9%, transparent); + color: color-mix(in oklch, var(--text) 88%, var(--accent)); +} + +.support-topic-links a:hover::after { + transform: translateX(3px); +} + +.support-topic-links a:focus-visible { + position: relative; + z-index: 1; + outline: 3px solid color-mix(in oklch, var(--accent) 50%, transparent); + outline-offset: -3px; + background: color-mix(in oklch, var(--accent) 12%, transparent); } .support-anchor-section { @@ -283,28 +329,105 @@ .support-actions .btn { min-width: 180px; + justify-content: center; + text-align: center; +} + +.support-actions-trust { + margin: 1.5rem 0; +} + +.support-details { + margin-top: 1.25rem; + border: 1px solid var(--card-border); + border-radius: 16px; + background: color-mix(in oklch, var(--card) 82%, transparent); +} + +.support-details summary { + padding: 1rem 1.15rem; + color: var(--text); + cursor: pointer; + font-weight: 800; +} + +.support-details summary:focus-visible { + outline: 3px solid color-mix(in oklch, var(--accent) 45%, transparent); + outline-offset: 3px; + border-radius: 14px; +} + +.support-details[open] { + padding-bottom: 1rem; +} + +.support-details[open] summary { + margin-bottom: 0.25rem; +} + +.support-details p, +.support-details pre { + margin: 0.75rem 1.15rem 0; +} + +.support-details pre { + overflow-x: auto; + border: 1px solid color-mix(in oklch, var(--accent) 22%, var(--card-border)); + border-radius: 12px; + background: color-mix(in oklch, var(--bg) 82%, black); + padding: 1rem; + color: var(--text); + font-size: 0.82rem; + line-height: 1.6; +} + +.support-details code { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; } html.theme-light .support-card, -html.theme-light .support-callout { +html.theme-light .support-callout, +html.theme-light .support-details { background-color: var(--card-surface); box-shadow: 0 18px 45px rgba(44, 62, 37, 0.1); } +html.theme-light .support-details pre { + background: color-mix(in oklch, var(--bg) 92%, white); +} + @media (max-width: 720px) { .support-content { width: min(100% - 1rem, 980px); padding-top: 7.5rem; } - .support-grid { + .support-topic-grid { grid-template-columns: 1fr; } + .support-topic-index { + margin-bottom: 2.25rem; + } + .support-card { border-radius: 18px; } + .support-topic-header { + min-height: 0; + padding: 1.35rem 1.3rem 1rem; + } + + .support-topic-links a { + min-height: 3.65rem; + padding: 0.85rem 1.3rem; + } + + .support-topic-links a:hover { + padding-left: 1.4rem; + } + .support-callout { grid-template-columns: 1fr; } diff --git a/website/template.html b/website/template.html index dd6541b..ebe07f6 100644 --- a/website/template.html +++ b/website/template.html @@ -1418,6 +1418,10 @@ root * /var/www/koalasync/website/www encode zstd gzip + # Redirect legacy support-page URLs to their canonical clean URLs + redir /help.html /help 308 + redir /site-access-help.html /site-access-help 308 + # Clean URLs support (no .html required) try_files {path} {path}.html {path}/ file_server @@ -1450,6 +1454,10 @@ encode zstd gzip root * /var/www/koalasync/website/www + # Redirect legacy support-page URLs to their canonical clean URLs + redir /help.html /help 308 + redir /site-access-help.html /site-access-help 308 + # Clean URLs: Resolves paths without .html in the URL try_files {path} {path}.html {path}/ file_server