v6.8.8: added a section to support the project
CodeQL Analysis / Analyze CodeQL (push) Canceled after 0s
Deploy Application / deploy (push) Canceled after 0s
Mirror to Codeberg / mirror (push) Canceled after 0s
Mirror to PrivacyGuides / mirror (push) Canceled after 0s

This commit is contained in:
lockbitchat committed 2026-09-24 00:52:14 -04:00
1 parent eac3009339
commit cd9d78d398
60 files changed
+943 -292

No files matched your search

+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## v6.8.8 — Project support section
Added a section to support the project.
## v6.8.6 — Desktop 1.0.4 downloads, and a simpler roadmap ## v6.8.6 — Desktop 1.0.4 downloads, and a simpler roadmap
The download buttons now give you desktop version 1.0.4, which fetches its relay The download buttons now give you desktop version 1.0.4, which fetches its relay
+1 -1
View File
@@ -9,7 +9,7 @@
No accounts. No servers storing your messages. No installation required. No accounts. No servers storing your messages. No installation required.
[![License: MIT](https://img.shields.io/badge/License-MIT-f0892a.svg)](LICENSE) [![License: MIT](https://img.shields.io/badge/License-MIT-f0892a.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-6.8.6-3ecf8e.svg)](CHANGELOG.md) [![Version](https://img.shields.io/badge/version-6.8.8-3ecf8e.svg)](CHANGELOG.md)
[![Get it from the Snap Store](https://snapcraft.io/securebit-chat/badge.svg)](https://snapcraft.io/securebit-chat) [![Get it from the Snap Store](https://snapcraft.io/securebit-chat/badge.svg)](https://snapcraft.io/securebit-chat)
[![PWA](https://img.shields.io/badge/PWA-installable-3ecf8e.svg)](#install-as-an-app) [![PWA](https://img.shields.io/badge/PWA-installable-3ecf8e.svg)](#install-as-an-app)
[![Encryption](https://img.shields.io/badge/crypto-ECDH%20P--384%20%C2%B7%20AES--256--GCM-blue.svg)](#security-model) [![Encryption](https://img.shields.io/badge/crypto-ECDH%20P--384%20%C2%B7%20AES--256--GCM-blue.svg)](#security-model)
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/ar/manifest.json"> <link rel="manifest" href="/ar/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/ar.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/ar.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#F7931A"/><path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/></g></svg>

After

Width:  |  Height:  |  Size: 953 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#627EEA"/><g fill="#FFF" fill-rule="nonzero"><path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/><path d="M16.498 4L9 16.22l7.498-3.35z"/><path fill-opacity=".602" d="M16.498 21.968v6.027L24 17.616z"/><path d="M16.498 27.995v-6.028L9 17.616z"/><path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/><path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 525 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 37 37" shape-rendering="crispEdges"><path fill="#111114" d="M0 0h7v1h-7zM8 0h2v1h-2zM11 0h2v1h-2zM14 0h1v1h-1zM16 0h1v1h-1zM18 0h1v1h-1zM20 0h6v1h-6zM28 0h1v1h-1zM30 0h7v1h-7zM0 1h1v1h-1zM6 1h1v1h-1zM8 1h1v1h-1zM11 1h1v1h-1zM14 1h2v1h-2zM18 1h2v1h-2zM25 1h3v1h-3zM30 1h1v1h-1zM36 1h1v1h-1zM0 2h1v1h-1zM2 2h3v1h-3zM6 2h1v1h-1zM8 2h1v1h-1zM10 2h2v1h-2zM13 2h1v1h-1zM15 2h1v1h-1zM20 2h1v1h-1zM22 2h1v1h-1zM28 2h1v1h-1zM30 2h1v1h-1zM32 2h3v1h-3zM36 2h1v1h-1zM0 3h1v1h-1zM2 3h3v1h-3zM6 3h1v1h-1zM14 3h1v1h-1zM16 3h8v1h-8zM25 3h4v1h-4zM30 3h1v1h-1zM32 3h3v1h-3zM36 3h1v1h-1zM0 4h1v1h-1zM2 4h3v1h-3zM6 4h1v1h-1zM10 4h2v1h-2zM18 4h7v1h-7zM26 4h1v1h-1zM30 4h1v1h-1zM32 4h3v1h-3zM36 4h1v1h-1zM0 5h1v1h-1zM6 5h1v1h-1zM8 5h3v1h-3zM12 5h1v1h-1zM15 5h4v1h-4zM20 5h1v1h-1zM23 5h1v1h-1zM27 5h2v1h-2zM30 5h1v1h-1zM36 5h1v1h-1zM0 6h7v1h-7zM8 6h1v1h-1zM10 6h1v1h-1zM12 6h1v1h-1zM14 6h1v1h-1zM16 6h1v1h-1zM18 6h1v1h-1zM20 6h1v1h-1zM22 6h1v1h-1zM24 6h1v1h-1zM26 6h1v1h-1zM28 6h1v1h-1zM30 6h7v1h-7zM8 7h1v1h-1zM11 7h3v1h-3zM15 7h2v1h-2zM18 7h1v1h-1zM22 7h1v1h-1zM24 7h1v1h-1zM2 8h3v1h-3zM6 8h1v1h-1zM8 8h3v1h-3zM12 8h1v1h-1zM17 8h1v1h-1zM22 8h1v1h-1zM25 8h7v1h-7zM34 8h3v1h-3zM0 9h1v1h-1zM2 9h2v1h-2zM5 9h1v1h-1zM7 9h3v1h-3zM11 9h3v1h-3zM15 9h3v1h-3zM20 9h2v1h-2zM24 9h1v1h-1zM26 9h1v1h-1zM28 9h2v1h-2zM33 9h3v1h-3zM0 10h4v1h-4zM6 10h2v1h-2zM11 10h2v1h-2zM15 10h3v1h-3zM21 10h1v1h-1zM24 10h2v1h-2zM27 10h1v1h-1zM30 10h2v1h-2zM34 10h3v1h-3zM1 11h3v1h-3zM5 11h1v1h-1zM7 11h1v1h-1zM9 11h2v1h-2zM12 11h2v1h-2zM15 11h4v1h-4zM20 11h2v1h-2zM27 11h1v1h-1zM29 11h2v1h-2zM3 12h2v1h-2zM6 12h1v1h-1zM8 12h1v1h-1zM11 12h1v1h-1zM13 12h2v1h-2zM16 12h1v1h-1zM20 12h2v1h-2zM25 12h2v1h-2zM29 12h6v1h-6zM36 12h1v1h-1zM1 13h2v1h-2zM4 13h2v1h-2zM8 13h1v1h-1zM11 13h9v1h-9zM21 13h1v1h-1zM23 13h4v1h-4zM28 13h1v1h-1zM33 13h1v1h-1zM35 13h1v1h-1zM0 14h3v1h-3zM6 14h4v1h-4zM11 14h3v1h-3zM17 14h2v1h-2zM26 14h3v1h-3zM32 14h5v1h-5zM1 15h2v1h-2zM4 15h1v1h-1zM7 15h1v1h-1zM13 15h4v1h-4zM19 15h1v1h-1zM21 15h3v1h-3zM28 15h1v1h-1zM30 15h2v1h-2zM35 15h1v1h-1zM0 16h7v1h-7zM9 16h2v1h-2zM12 16h2v1h-2zM17 16h3v1h-3zM21 16h1v1h-1zM23 16h1v1h-1zM25 16h12v1h-12zM0 17h1v1h-1zM3 17h3v1h-3zM7 17h1v1h-1zM11 17h2v1h-2zM14 17h1v1h-1zM20 17h1v1h-1zM23 17h1v1h-1zM26 17h1v1h-1zM31 17h1v1h-1zM33 17h1v1h-1zM0 18h1v1h-1zM2 18h1v1h-1zM4 18h1v1h-1zM6 18h1v1h-1zM8 18h2v1h-2zM11 18h1v1h-1zM13 18h1v1h-1zM20 18h2v1h-2zM23 18h3v1h-3zM27 18h4v1h-4zM33 18h1v1h-1zM35 18h2v1h-2zM0 19h4v1h-4zM5 19h1v1h-1zM7 19h1v1h-1zM11 19h4v1h-4zM16 19h2v1h-2zM20 19h2v1h-2zM26 19h2v1h-2zM29 19h2v1h-2zM33 19h1v1h-1zM2 20h2v1h-2zM5 20h3v1h-3zM11 20h2v1h-2zM16 20h3v1h-3zM20 20h2v1h-2zM23 20h4v1h-4zM30 20h3v1h-3zM34 20h1v1h-1zM2 21h2v1h-2zM5 21h1v1h-1zM7 21h3v1h-3zM11 21h3v1h-3zM16 21h1v1h-1zM18 21h2v1h-2zM22 21h2v1h-2zM28 21h2v1h-2zM31 21h1v1h-1zM2 22h1v1h-1zM4 22h1v1h-1zM6 22h2v1h-2zM9 22h2v1h-2zM14 22h3v1h-3zM19 22h7v1h-7zM27 22h2v1h-2zM31 22h1v1h-1zM33 22h1v1h-1zM35 22h2v1h-2zM3 23h1v1h-1zM5 23h1v1h-1zM7 23h1v1h-1zM10 23h2v1h-2zM13 23h2v1h-2zM16 23h1v1h-1zM19 23h1v1h-1zM23 23h1v1h-1zM27 23h4v1h-4zM1 24h2v1h-2zM4 24h4v1h-4zM9 24h1v1h-1zM11 24h2v1h-2zM15 24h3v1h-3zM20 24h2v1h-2zM28 24h5v1h-5zM34 24h1v1h-1zM0 25h1v1h-1zM2 25h1v1h-1zM9 25h1v1h-1zM11 25h2v1h-2zM14 25h1v1h-1zM16 25h1v1h-1zM19 25h2v1h-2zM24 25h2v1h-2zM28 25h1v1h-1zM31 25h1v1h-1zM35 25h1v1h-1zM0 26h1v1h-1zM3 26h4v1h-4zM8 26h1v1h-1zM10 26h1v1h-1zM13 26h3v1h-3zM18 26h1v1h-1zM21 26h1v1h-1zM27 26h3v1h-3zM31 26h1v1h-1zM34 26h1v1h-1zM36 26h1v1h-1zM0 27h1v1h-1zM2 27h1v1h-1zM4 27h1v1h-1zM9 27h1v1h-1zM12 27h4v1h-4zM19 27h1v1h-1zM21 27h2v1h-2zM24 27h1v1h-1zM0 28h1v1h-1zM4 28h4v1h-4zM10 28h4v1h-4zM15 28h4v1h-4zM20 28h1v1h-1zM25 28h2v1h-2zM28 28h9v1h-9zM8 29h2v1h-2zM12 29h1v1h-1zM14 29h6v1h-6zM23 29h1v1h-1zM25 29h1v1h-1zM28 29h1v1h-1zM32 29h2v1h-2zM35 29h1v1h-1zM0 30h7v1h-7zM9 30h2v1h-2zM12 30h1v1h-1zM14 30h1v1h-1zM17 30h4v1h-4zM25 30h1v1h-1zM28 30h1v1h-1zM30 30h1v1h-1zM32 30h1v1h-1zM34 30h1v1h-1zM36 30h1v1h-1zM0 31h1v1h-1zM6 31h1v1h-1zM12 31h6v1h-6zM19 31h2v1h-2zM22 31h2v1h-2zM28 31h1v1h-1zM32 31h2v1h-2zM36 31h1v1h-1zM0 32h1v1h-1zM2 32h3v1h-3zM6 32h1v1h-1zM8 32h1v1h-1zM10 32h3v1h-3zM15 32h3v1h-3zM20 32h2v1h-2zM24 32h9v1h-9zM34 32h2v1h-2zM0 33h1v1h-1zM2 33h3v1h-3zM6 33h1v1h-1zM8 33h7v1h-7zM18 33h1v1h-1zM25 33h1v1h-1zM27 33h4v1h-4zM32 33h2v1h-2zM0 34h1v1h-1zM2 34h3v1h-3zM6 34h1v1h-1zM8 34h2v1h-2zM12 34h1v1h-1zM16 34h1v1h-1zM21 34h4v1h-4zM26 34h2v1h-2zM31 34h1v1h-1zM33 34h1v1h-1zM36 34h1v1h-1zM0 35h1v1h-1zM6 35h1v1h-1zM9 35h2v1h-2zM12 35h3v1h-3zM16 35h1v1h-1zM19 35h2v1h-2zM22 35h2v1h-2zM28 35h1v1h-1zM30 35h2v1h-2zM36 35h1v1h-1zM0 36h7v1h-7zM12 36h1v1h-1zM14 36h1v1h-1zM19 36h1v1h-1zM22 36h5v1h-5zM29 36h1v1h-1zM32 36h1v1h-1zM34 36h3v1h-3z"/></svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 37 37" shape-rendering="crispEdges"><path fill="#111114" d="M0 0h7v1h-7zM8 0h3v1h-3zM16 0h4v1h-4zM22 0h3v1h-3zM27 0h2v1h-2zM30 0h7v1h-7zM0 1h1v1h-1zM6 1h1v1h-1zM8 1h1v1h-1zM11 1h1v1h-1zM15 1h1v1h-1zM17 1h1v1h-1zM19 1h1v1h-1zM22 1h1v1h-1zM25 1h1v1h-1zM27 1h2v1h-2zM30 1h1v1h-1zM36 1h1v1h-1zM0 2h1v1h-1zM2 2h3v1h-3zM6 2h1v1h-1zM8 2h1v1h-1zM11 2h1v1h-1zM13 2h2v1h-2zM16 2h1v1h-1zM18 2h2v1h-2zM21 2h3v1h-3zM26 2h1v1h-1zM28 2h1v1h-1zM30 2h1v1h-1zM32 2h3v1h-3zM36 2h1v1h-1zM0 3h1v1h-1zM2 3h3v1h-3zM6 3h1v1h-1zM9 3h1v1h-1zM13 3h1v1h-1zM15 3h2v1h-2zM21 3h2v1h-2zM25 3h1v1h-1zM27 3h1v1h-1zM30 3h1v1h-1zM32 3h3v1h-3zM36 3h1v1h-1zM0 4h1v1h-1zM2 4h3v1h-3zM6 4h1v1h-1zM17 4h1v1h-1zM19 4h1v1h-1zM23 4h2v1h-2zM26 4h1v1h-1zM28 4h1v1h-1zM30 4h1v1h-1zM32 4h3v1h-3zM36 4h1v1h-1zM0 5h1v1h-1zM6 5h1v1h-1zM8 5h2v1h-2zM11 5h3v1h-3zM16 5h4v1h-4zM21 5h3v1h-3zM25 5h3v1h-3zM30 5h1v1h-1zM36 5h1v1h-1zM0 6h7v1h-7zM8 6h1v1h-1zM10 6h1v1h-1zM12 6h1v1h-1zM14 6h1v1h-1zM16 6h1v1h-1zM18 6h1v1h-1zM20 6h1v1h-1zM22 6h1v1h-1zM24 6h1v1h-1zM26 6h1v1h-1zM28 6h1v1h-1zM30 6h7v1h-7zM8 7h2v1h-2zM11 7h2v1h-2zM15 7h2v1h-2zM19 7h3v1h-3zM28 7h1v1h-1zM2 8h3v1h-3zM6 8h1v1h-1zM8 8h1v1h-1zM12 8h1v1h-1zM14 8h2v1h-2zM17 8h2v1h-2zM24 8h2v1h-2zM27 8h1v1h-1zM29 8h3v1h-3zM34 8h3v1h-3zM0 9h2v1h-2zM3 9h3v1h-3zM7 9h2v1h-2zM14 9h1v1h-1zM18 9h2v1h-2zM22 9h2v1h-2zM26 9h1v1h-1zM28 9h2v1h-2zM36 9h1v1h-1zM1 10h2v1h-2zM5 10h2v1h-2zM8 10h3v1h-3zM13 10h1v1h-1zM17 10h2v1h-2zM20 10h2v1h-2zM23 10h2v1h-2zM27 10h4v1h-4zM32 10h1v1h-1zM35 10h2v1h-2zM4 11h2v1h-2zM7 11h2v1h-2zM11 11h1v1h-1zM13 11h2v1h-2zM20 11h2v1h-2zM24 11h1v1h-1zM26 11h2v1h-2zM30 11h2v1h-2zM33 11h1v1h-1zM0 12h4v1h-4zM5 12h2v1h-2zM8 12h2v1h-2zM11 12h1v1h-1zM15 12h2v1h-2zM18 12h1v1h-1zM23 12h2v1h-2zM26 12h2v1h-2zM29 12h2v1h-2zM34 12h1v1h-1zM36 12h1v1h-1zM1 13h1v1h-1zM3 13h3v1h-3zM10 13h4v1h-4zM19 13h2v1h-2zM22 13h1v1h-1zM28 13h1v1h-1zM31 13h1v1h-1zM33 13h1v1h-1zM36 13h1v1h-1zM0 14h1v1h-1zM6 14h1v1h-1zM10 14h4v1h-4zM15 14h4v1h-4zM20 14h2v1h-2zM23 14h3v1h-3zM27 14h1v1h-1zM30 14h2v1h-2zM33 14h1v1h-1zM35 14h2v1h-2zM0 15h2v1h-2zM5 15h1v1h-1zM8 15h5v1h-5zM17 15h1v1h-1zM19 15h2v1h-2zM22 15h1v1h-1zM26 15h1v1h-1zM29 15h3v1h-3zM35 15h2v1h-2zM1 16h2v1h-2zM5 16h3v1h-3zM9 16h4v1h-4zM16 16h4v1h-4zM22 16h1v1h-1zM25 16h1v1h-1zM27 16h2v1h-2zM30 16h1v1h-1zM32 16h1v1h-1zM34 16h3v1h-3zM0 17h2v1h-2zM5 17h1v1h-1zM7 17h3v1h-3zM13 17h2v1h-2zM16 17h4v1h-4zM26 17h1v1h-1zM29 17h1v1h-1zM31 17h1v1h-1zM33 17h1v1h-1zM35 17h1v1h-1zM1 18h2v1h-2zM4 18h1v1h-1zM6 18h4v1h-4zM11 18h2v1h-2zM16 18h1v1h-1zM18 18h4v1h-4zM23 18h1v1h-1zM27 18h4v1h-4zM32 18h1v1h-1zM34 18h1v1h-1zM36 18h1v1h-1zM0 19h1v1h-1zM3 19h3v1h-3zM7 19h1v1h-1zM11 19h4v1h-4zM17 19h1v1h-1zM21 19h4v1h-4zM26 19h3v1h-3zM31 19h1v1h-1zM33 19h1v1h-1zM0 20h2v1h-2zM3 20h4v1h-4zM8 20h1v1h-1zM11 20h1v1h-1zM18 20h10v1h-10zM29 20h2v1h-2zM34 20h1v1h-1zM0 21h1v1h-1zM2 21h2v1h-2zM9 21h1v1h-1zM13 21h2v1h-2zM17 21h2v1h-2zM23 21h1v1h-1zM28 21h1v1h-1zM31 21h1v1h-1zM33 21h1v1h-1zM35 21h1v1h-1zM1 22h1v1h-1zM3 22h4v1h-4zM8 22h2v1h-2zM11 22h3v1h-3zM15 22h1v1h-1zM18 22h2v1h-2zM23 22h5v1h-5zM30 22h3v1h-3zM36 22h1v1h-1zM1 23h1v1h-1zM4 23h1v1h-1zM8 23h2v1h-2zM11 23h2v1h-2zM14 23h4v1h-4zM20 23h1v1h-1zM24 23h1v1h-1zM26 23h5v1h-5zM33 23h1v1h-1zM35 23h1v1h-1zM1 24h1v1h-1zM3 24h4v1h-4zM8 24h1v1h-1zM10 24h2v1h-2zM13 24h3v1h-3zM18 24h2v1h-2zM23 24h1v1h-1zM27 24h1v1h-1zM29 24h2v1h-2zM32 24h1v1h-1zM34 24h3v1h-3zM0 25h2v1h-2zM5 25h1v1h-1zM7 25h3v1h-3zM11 25h3v1h-3zM16 25h1v1h-1zM21 25h2v1h-2zM24 25h1v1h-1zM27 25h2v1h-2zM31 25h1v1h-1zM33 25h1v1h-1zM35 25h1v1h-1zM0 26h1v1h-1zM2 26h1v1h-1zM6 26h1v1h-1zM8 26h1v1h-1zM10 26h1v1h-1zM12 26h1v1h-1zM15 26h6v1h-6zM22 26h1v1h-1zM26 26h2v1h-2zM29 26h2v1h-2zM32 26h1v1h-1zM36 26h1v1h-1zM0 27h1v1h-1zM2 27h3v1h-3zM9 27h1v1h-1zM12 27h3v1h-3zM16 27h1v1h-1zM18 27h1v1h-1zM24 27h1v1h-1zM27 27h2v1h-2zM36 27h1v1h-1zM0 28h1v1h-1zM2 28h1v1h-1zM4 28h1v1h-1zM6 28h1v1h-1zM13 28h2v1h-2zM16 28h1v1h-1zM19 28h1v1h-1zM21 28h2v1h-2zM25 28h1v1h-1zM27 28h10v1h-10zM8 29h1v1h-1zM14 29h2v1h-2zM17 29h1v1h-1zM19 29h1v1h-1zM22 29h3v1h-3zM26 29h3v1h-3zM32 29h3v1h-3zM36 29h1v1h-1zM0 30h7v1h-7zM9 30h1v1h-1zM14 30h6v1h-6zM24 30h2v1h-2zM28 30h1v1h-1zM30 30h1v1h-1zM32 30h2v1h-2zM35 30h2v1h-2zM0 31h1v1h-1zM6 31h1v1h-1zM13 31h1v1h-1zM16 31h1v1h-1zM19 31h1v1h-1zM21 31h4v1h-4zM26 31h1v1h-1zM28 31h1v1h-1zM32 31h1v1h-1zM36 31h1v1h-1zM0 32h1v1h-1zM2 32h3v1h-3zM6 32h1v1h-1zM8 32h4v1h-4zM14 32h3v1h-3zM19 32h1v1h-1zM21 32h1v1h-1zM24 32h10v1h-10zM35 32h2v1h-2zM0 33h1v1h-1zM2 33h3v1h-3zM6 33h1v1h-1zM8 33h3v1h-3zM12 33h1v1h-1zM14 33h1v1h-1zM18 33h3v1h-3zM29 33h2v1h-2zM33 33h1v1h-1zM0 34h1v1h-1zM2 34h3v1h-3zM6 34h1v1h-1zM8 34h1v1h-1zM12 34h1v1h-1zM15 34h1v1h-1zM18 34h1v1h-1zM20 34h1v1h-1zM22 34h4v1h-4zM27 34h1v1h-1zM30 34h2v1h-2zM36 34h1v1h-1zM0 35h1v1h-1zM6 35h1v1h-1zM12 35h1v1h-1zM15 35h2v1h-2zM18 35h1v1h-1zM20 35h4v1h-4zM26 35h1v1h-1zM28 35h5v1h-5zM36 35h1v1h-1zM0 36h7v1h-7zM10 36h5v1h-5zM16 36h1v1h-1zM19 36h1v1h-1zM22 36h6v1h-6zM31 36h1v1h-1zM33 36h4v1h-4z"/></svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33 33" shape-rendering="crispEdges"><path fill="#111114" d="M0 0h7v1h-7zM9 0h1v1h-1zM11 0h1v1h-1zM14 0h2v1h-2zM17 0h1v1h-1zM21 0h2v1h-2zM26 0h7v1h-7zM0 1h1v1h-1zM6 1h1v1h-1zM8 1h1v1h-1zM10 1h1v1h-1zM12 1h2v1h-2zM15 1h1v1h-1zM17 1h1v1h-1zM19 1h2v1h-2zM24 1h1v1h-1zM26 1h1v1h-1zM32 1h1v1h-1zM0 2h1v1h-1zM2 2h3v1h-3zM6 2h1v1h-1zM8 2h1v1h-1zM10 2h3v1h-3zM14 2h1v1h-1zM17 2h2v1h-2zM21 2h1v1h-1zM26 2h1v1h-1zM28 2h3v1h-3zM32 2h1v1h-1zM0 3h1v1h-1zM2 3h3v1h-3zM6 3h1v1h-1zM8 3h2v1h-2zM12 3h1v1h-1zM14 3h2v1h-2zM18 3h2v1h-2zM21 3h4v1h-4zM26 3h1v1h-1zM28 3h3v1h-3zM32 3h1v1h-1zM0 4h1v1h-1zM2 4h3v1h-3zM6 4h1v1h-1zM8 4h1v1h-1zM10 4h1v1h-1zM12 4h1v1h-1zM14 4h3v1h-3zM19 4h2v1h-2zM23 4h1v1h-1zM26 4h1v1h-1zM28 4h3v1h-3zM32 4h1v1h-1zM0 5h1v1h-1zM6 5h1v1h-1zM8 5h2v1h-2zM11 5h4v1h-4zM17 5h2v1h-2zM20 5h2v1h-2zM24 5h1v1h-1zM26 5h1v1h-1zM32 5h1v1h-1zM0 6h7v1h-7zM8 6h1v1h-1zM10 6h1v1h-1zM12 6h1v1h-1zM14 6h1v1h-1zM16 6h1v1h-1zM18 6h1v1h-1zM20 6h1v1h-1zM22 6h1v1h-1zM24 6h1v1h-1zM26 6h7v1h-7zM9 7h6v1h-6zM17 7h1v1h-1zM20 7h1v1h-1zM2 8h1v1h-1zM5 8h5v1h-5zM11 8h1v1h-1zM16 8h1v1h-1zM18 8h1v1h-1zM23 8h3v1h-3zM27 8h5v1h-5zM1 9h1v1h-1zM3 9h1v1h-1zM5 9h1v1h-1zM7 9h1v1h-1zM9 9h2v1h-2zM14 9h2v1h-2zM17 9h1v1h-1zM19 9h1v1h-1zM21 9h1v1h-1zM24 9h4v1h-4zM30 9h1v1h-1zM0 10h3v1h-3zM4 10h3v1h-3zM8 10h4v1h-4zM14 10h2v1h-2zM18 10h6v1h-6zM25 10h1v1h-1zM30 10h1v1h-1zM32 10h1v1h-1zM2 11h4v1h-4zM7 11h1v1h-1zM9 11h1v1h-1zM11 11h2v1h-2zM14 11h2v1h-2zM17 11h4v1h-4zM22 11h1v1h-1zM24 11h1v1h-1zM26 11h4v1h-4zM31 11h1v1h-1zM0 12h3v1h-3zM5 12h3v1h-3zM12 12h2v1h-2zM15 12h1v1h-1zM19 12h2v1h-2zM23 12h1v1h-1zM26 12h1v1h-1zM28 12h1v1h-1zM32 12h1v1h-1zM0 13h1v1h-1zM2 13h1v1h-1zM11 13h1v1h-1zM13 13h1v1h-1zM17 13h1v1h-1zM19 13h2v1h-2zM22 13h1v1h-1zM24 13h1v1h-1zM27 13h1v1h-1zM29 13h1v1h-1zM31 13h1v1h-1zM1 14h2v1h-2zM4 14h1v1h-1zM6 14h1v1h-1zM10 14h2v1h-2zM14 14h3v1h-3zM18 14h1v1h-1zM20 14h1v1h-1zM22 14h2v1h-2zM25 14h1v1h-1zM27 14h3v1h-3zM32 14h1v1h-1zM0 15h2v1h-2zM3 15h2v1h-2zM7 15h3v1h-3zM11 15h1v1h-1zM13 15h1v1h-1zM16 15h1v1h-1zM22 15h1v1h-1zM29 15h1v1h-1zM32 15h1v1h-1zM0 16h1v1h-1zM3 16h1v1h-1zM6 16h3v1h-3zM10 16h1v1h-1zM14 16h2v1h-2zM17 16h2v1h-2zM23 16h2v1h-2zM26 16h2v1h-2zM31 16h2v1h-2zM0 17h1v1h-1zM2 17h2v1h-2zM9 17h2v1h-2zM12 17h4v1h-4zM17 17h3v1h-3zM23 17h2v1h-2zM26 17h2v1h-2zM29 17h1v1h-1zM31 17h2v1h-2zM0 18h3v1h-3zM4 18h4v1h-4zM9 18h1v1h-1zM12 18h3v1h-3zM17 18h4v1h-4zM23 18h3v1h-3zM28 18h3v1h-3zM32 18h1v1h-1zM1 19h2v1h-2zM5 19h1v1h-1zM8 19h2v1h-2zM11 19h1v1h-1zM14 19h3v1h-3zM20 19h1v1h-1zM24 19h1v1h-1zM27 19h1v1h-1zM29 19h1v1h-1zM31 19h1v1h-1zM1 20h1v1h-1zM3 20h1v1h-1zM5 20h2v1h-2zM8 20h2v1h-2zM13 20h2v1h-2zM16 20h2v1h-2zM21 20h1v1h-1zM23 20h1v1h-1zM26 20h5v1h-5zM2 21h2v1h-2zM5 21h1v1h-1zM7 21h9v1h-9zM17 21h4v1h-4zM22 21h1v1h-1zM24 21h1v1h-1zM27 21h1v1h-1zM29 21h1v1h-1zM31 21h1v1h-1zM0 22h2v1h-2zM3 22h1v1h-1zM6 22h5v1h-5zM15 22h3v1h-3zM19 22h1v1h-1zM21 22h1v1h-1zM23 22h4v1h-4zM30 22h1v1h-1zM32 22h1v1h-1zM5 23h1v1h-1zM7 23h5v1h-5zM15 23h2v1h-2zM18 23h1v1h-1zM20 23h1v1h-1zM23 23h2v1h-2zM27 23h1v1h-1zM29 23h1v1h-1zM31 23h1v1h-1zM0 24h2v1h-2zM4 24h5v1h-5zM10 24h1v1h-1zM16 24h1v1h-1zM18 24h2v1h-2zM21 24h1v1h-1zM24 24h5v1h-5zM31 24h2v1h-2zM8 25h1v1h-1zM10 25h1v1h-1zM17 25h2v1h-2zM20 25h1v1h-1zM23 25h2v1h-2zM28 25h1v1h-1zM31 25h2v1h-2zM0 26h7v1h-7zM8 26h1v1h-1zM10 26h12v1h-12zM23 26h2v1h-2zM26 26h1v1h-1zM28 26h1v1h-1zM31 26h2v1h-2zM0 27h1v1h-1zM6 27h1v1h-1zM8 27h2v1h-2zM14 27h2v1h-2zM18 27h3v1h-3zM22 27h3v1h-3zM28 27h2v1h-2zM31 27h2v1h-2zM0 28h1v1h-1zM2 28h3v1h-3zM6 28h1v1h-1zM9 28h1v1h-1zM14 28h3v1h-3zM18 28h1v1h-1zM20 28h9v1h-9zM0 29h1v1h-1zM2 29h3v1h-3zM6 29h1v1h-1zM10 29h2v1h-2zM13 29h1v1h-1zM15 29h3v1h-3zM20 29h1v1h-1zM24 29h2v1h-2zM28 29h1v1h-1zM30 29h1v1h-1zM32 29h1v1h-1zM0 30h1v1h-1zM2 30h3v1h-3zM6 30h1v1h-1zM8 30h1v1h-1zM10 30h4v1h-4zM15 30h1v1h-1zM19 30h5v1h-5zM25 30h2v1h-2zM28 30h2v1h-2zM31 30h2v1h-2zM0 31h1v1h-1zM6 31h1v1h-1zM10 31h2v1h-2zM13 31h1v1h-1zM15 31h1v1h-1zM19 31h2v1h-2zM23 31h2v1h-2zM27 31h1v1h-1zM29 31h1v1h-1zM0 32h7v1h-7zM18 32h1v1h-1zM23 32h3v1h-3zM30 32h1v1h-1zM32 32h1v1h-1z"/></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><g fill="none"><circle fill="#EF0027" cx="16" cy="16" r="16"/><path d="M21.932 9.913L7.5 7.257l7.595 19.112 10.583-12.894-3.746-3.562zm-.232 1.17l2.208 2.099-6.038 1.093 3.83-3.192zm-5.142 2.973l-6.364-5.278 10.402 1.914-4.038 3.364zm-.453.934l-1.038 8.58L9.472 9.487l6.633 5.502zm.96.455l6.687-1.21-7.67 9.343.983-8.133z" fill="#FFF"/></g></svg>

After

Width:  |  Height:  |  Size: 429 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#26A17B"/><path fill="#FFF" d="M17.922 17.383v-.002c-.11.008-.677.042-1.942.042-1.01 0-1.721-.03-1.971-.042v.003c-3.888-.171-6.79-.848-6.79-1.658 0-.809 2.902-1.486 6.79-1.66v2.644c.254.018.982.061 1.988.061 1.207 0 1.812-.05 1.925-.06v-2.643c3.88.173 6.775.85 6.775 1.658 0 .81-2.895 1.485-6.775 1.657m0-3.59v-2.366h5.414V7.819H8.595v3.608h5.414v2.365c-4.4.202-7.709 1.074-7.709 2.118 0 1.044 3.309 1.915 7.709 2.118v7.582h3.913v-7.584c4.393-.202 7.694-1.073 7.694-2.116 0-1.043-3.301-1.914-7.694-2.117"/></g></svg>

After

Width:  |  Height:  |  Size: 648 B

+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/de/manifest.json"> <link rel="manifest" href="/de/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/de.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/de.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+20 -20
View File
@@ -1,12 +1,12 @@
var en=Object.create;var fi=Object.defineProperty;var tn=Object.getOwnPropertyDescriptor;var rn=Object.getOwnPropertyNames;var sn=Object.getPrototypeOf,nn=Object.prototype.hasOwnProperty;var pi=(n,e)=>()=>{try{return e||n((e={exports:{}}).exports,e),e.exports}catch(t){throw e=0,t}};var an=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of rn(e))!nn.call(n,i)&&i!==t&&fi(n,i,{get:()=>e[i],enumerable:!(r=tn(e,i))||r.enumerable});return n};var yi=(n,e,t)=>(t=n!=null?en(sn(n)):{},an(e||!n||!n.__esModule?fi(t,"default",{value:n,enumerable:!0}):t,n));var Cs=pi((xo,qt)=>{var wt=class{constructor(e={}){this.permission=typeof Notification<"u"&&Notification&&typeof Notification.permission=="string"?Notification.permission:"denied",this.isTabActive=this.checkTabActive(),this.unreadCount=0,this.originalTitle=document.title,this.notificationQueue=[],this.maxQueueSize=e.maxQueueSize||5,this.rateLimitMs=e.rateLimitMs||2e3,this.lastNotificationTime=0,this.trustedOrigins=e.trustedOrigins||[],this.isSecureContext=window.isSecureContext,this.hidden=this.getHiddenProperty(),this.visibilityChange=this.getVisibilityChangeEvent(),this.initVisibilityTracking(),this.initSecurityChecks()}initSecurityChecks(){}getHiddenProperty(){return typeof document.hidden<"u"?"hidden":typeof document.msHidden<"u"?"msHidden":typeof document.webkitHidden<"u"?"webkitHidden":"hidden"}getVisibilityChangeEvent(){return typeof document.hidden<"u"?"visibilitychange":typeof document.msHidden<"u"?"msvisibilitychange":typeof document.webkitHidden<"u"?"webkitvisibilitychange":"visibilitychange"}checkTabActive(){return this.hidden&&typeof document[this.hidden]<"u"?!document[this.hidden]:typeof document.hasFocus=="function"?document.hasFocus():!0}initVisibilityTracking(){typeof document.addEventListener<"u"&&typeof document[this.hidden]<"u"&&document.addEventListener(this.visibilityChange,()=>{this.isTabActive=this.checkTabActive(),this.isTabActive&&(this.resetUnreadCount(),this.clearNotificationQueue())}),window.addEventListener("focus",()=>{this.isTabActive=this.checkTabActive(),this.isTabActive&&this.resetUnreadCount()}),window.addEventListener("blur",()=>{this.isTabActive=this.checkTabActive()}),window.addEventListener("beforeunload",()=>{this.clearNotificationQueue()})}async requestPermission(){if(!this.isSecureContext||!("Notification"in window))return!1;if(this.permission==="granted")return!0;if(this.permission==="denied")return!1;try{return this.permission=await Notification.requestPermission(),this.permission==="granted"}catch{return!1}}updateTitle(){this.unreadCount>0?document.title=`(${this.unreadCount}) ${this.originalTitle}`:document.title=this.originalTitle}sanitizeText(e){if(typeof e!="string")return"";let t=document.createElement("div");return t.textContent=e,t.innerHTML.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").substring(0,500)}validateIconUrl(e){if(!e)return null;try{let t=new URL(e,window.location.origin);return t.protocol==="https:"||t.protocol==="data:"?this.trustedOrigins.length>0?this.trustedOrigins.some(i=>t.origin===i)?t.href:null:t.href:null}catch{return null}}checkRateLimit(){let e=Date.now();return e-this.lastNotificationTime<this.rateLimitMs?!1:(this.lastNotificationTime=e,!0)}notify(e,t,r={}){if(typeof Notification>"u"||(this.isTabActive=this.checkTabActive(),this.isTabActive)||this.permission!=="granted"||!this.checkRateLimit())return null;let i=this.sanitizeText(e||"Unknown"),s=this.sanitizeText(t||""),a=this.validateIconUrl(r.icon)||"/logo/icon-192x192.png";this.notificationQueue.length>=this.maxQueueSize&&this.clearNotificationQueue();try{let o=new Notification(`${i}`,{body:s.substring(0,200),icon:a,badge:a,tag:`chat-${r.senderId||"unknown"}`,requireInteraction:!1,silent:r.silent||!1,vibrate:navigator.vibrate?[200,100,200]:void 0,data:{senderId:this.sanitizeText(r.senderId),timestamp:Date.now()}});this.unreadCount++,this.updateTitle(),this.notificationQueue.push(o),o.onclick=d=>{if(d.preventDefault(),window.focus(),o.close(),typeof r.onClick=="function")try{r.onClick(r.senderId)}catch(u){console.error("[Notifications] Error in onClick handler:",u)}},o.onerror=d=>{console.error("[Notifications] Error showing notification:",d)};let c=Math.min(r.autoClose||5e3,1e4);return setTimeout(()=>{o.close(),this.removeFromQueue(o)},c),o}catch(o){return console.error("[Notifications] Failed to create notification:",o),null}}removeFromQueue(e){let t=this.notificationQueue.indexOf(e);t>-1&&this.notificationQueue.splice(t,1)}clearNotificationQueue(){this.notificationQueue.forEach(e=>{try{e.close()}catch{}}),this.notificationQueue=[]}resetUnreadCount(){this.unreadCount=0,this.updateTitle()}getStatus(){return{permission:this.permission,isTabActive:this.isTabActive,unreadCount:this.unreadCount,isSecureContext:this.isSecureContext,queueSize:this.notificationQueue.length}}},jt=class{constructor(){this.notificationManager=new wt({maxQueueSize:5,rateLimitMs:2e3,trustedOrigins:[window.Line truncated var os=Object.create;var bi=Object.defineProperty;var cs=Object.getOwnPropertyDescriptor;var ls=Object.getOwnPropertyNames;var ds=Object.getPrototypeOf,us=Object.prototype.hasOwnProperty;var _i=(s,e)=>()=>{try{return e||s((e={exports:{}}).exports,e),e.exports}catch(t){throw e=0,t}};var hs=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of ls(e))!us.call(s,i)&&i!==t&&bi(s,i,{get:()=>e[i],enumerable:!(r=cs(e,i))||r.enumerable});return s};var wi=(s,e,t)=>(t=s!=null?os(ds(s)):{},hs(e||!s||!s.__esModule?bi(t,"default",{value:s,enumerable:!0}):t,s));var Ln=_i((Po,Wt)=>{var wt=class{constructor(e={}){this.permission=typeof Notification<"u"&&Notification&&typeof Notification.permission=="string"?Notification.permission:"denied",this.isTabActive=this.checkTabActive(),this.unreadCount=0,this.originalTitle=document.title,this.notificationQueue=[],this.maxQueueSize=e.maxQueueSize||5,this.rateLimitMs=e.rateLimitMs||2e3,this.lastNotificationTime=0,this.trustedOrigins=e.trustedOrigins||[],this.isSecureContext=window.isSecureContext,this.hidden=this.getHiddenProperty(),this.visibilityChange=this.getVisibilityChangeEvent(),this.initVisibilityTracking(),this.initSecurityChecks()}initSecurityChecks(){}getHiddenProperty(){return typeof document.hidden<"u"?"hidden":typeof document.msHidden<"u"?"msHidden":typeof document.webkitHidden<"u"?"webkitHidden":"hidden"}getVisibilityChangeEvent(){return typeof document.hidden<"u"?"visibilitychange":typeof document.msHidden<"u"?"msvisibilitychange":typeof document.webkitHidden<"u"?"webkitvisibilitychange":"visibilitychange"}checkTabActive(){return this.hidden&&typeof document[this.hidden]<"u"?!document[this.hidden]:typeof document.hasFocus=="function"?document.hasFocus():!0}initVisibilityTracking(){typeof document.addEventListener<"u"&&typeof document[this.hidden]<"u"&&document.addEventListener(this.visibilityChange,()=>{this.isTabActive=this.checkTabActive(),this.isTabActive&&(this.resetUnreadCount(),this.clearNotificationQueue())}),window.addEventListener("focus",()=>{this.isTabActive=this.checkTabActive(),this.isTabActive&&this.resetUnreadCount()}),window.addEventListener("blur",()=>{this.isTabActive=this.checkTabActive()}),window.addEventListener("beforeunload",()=>{this.clearNotificationQueue()})}async requestPermission(){if(!this.isSecureContext||!("Notification"in window))return!1;if(this.permission==="granted")return!0;if(this.permission==="denied")return!1;try{return this.permission=await Notification.requestPermission(),this.permission==="granted"}catch{return!1}}updateTitle(){this.unreadCount>0?document.title=`(${this.unreadCount}) ${this.originalTitle}`:document.title=this.originalTitle}sanitizeText(e){if(typeof e!="string")return"";let t=document.createElement("div");return t.textContent=e,t.innerHTML.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").substring(0,500)}validateIconUrl(e){if(!e)return null;try{let t=new URL(e,window.location.origin);return t.protocol==="https:"||t.protocol==="data:"?this.trustedOrigins.length>0?this.trustedOrigins.some(i=>t.origin===i)?t.href:null:t.href:null}catch{return null}}checkRateLimit(){let e=Date.now();return e-this.lastNotificationTime<this.rateLimitMs?!1:(this.lastNotificationTime=e,!0)}notify(e,t,r={}){if(typeof Notification>"u"||(this.isTabActive=this.checkTabActive(),this.isTabActive)||this.permission!=="granted"||!this.checkRateLimit())return null;let i=this.sanitizeText(e||"Unknown"),n=this.sanitizeText(t||""),a=this.validateIconUrl(r.icon)||"/logo/icon-192x192.png";this.notificationQueue.length>=this.maxQueueSize&&this.clearNotificationQueue();try{let o=new Notification(`${i}`,{body:n.substring(0,200),icon:a,badge:a,tag:`chat-${r.senderId||"unknown"}`,requireInteraction:!1,silent:r.silent||!1,vibrate:navigator.vibrate?[200,100,200]:void 0,data:{senderId:this.sanitizeText(r.senderId),timestamp:Date.now()}});this.unreadCount++,this.updateTitle(),this.notificationQueue.push(o),o.onclick=d=>{if(d.preventDefault(),window.focus(),o.close(),typeof r.onClick=="function")try{r.onClick(r.senderId)}catch(u){console.error("[Notifications] Error in onClick handler:",u)}},o.onerror=d=>{console.error("[Notifications] Error showing notification:",d)};let c=Math.min(r.autoClose||5e3,1e4);return setTimeout(()=>{o.close(),this.removeFromQueue(o)},c),o}catch(o){return console.error("[Notifications] Failed to create notification:",o),null}}removeFromQueue(e){let t=this.notificationQueue.indexOf(e);t>-1&&this.notificationQueue.splice(t,1)}clearNotificationQueue(){this.notificationQueue.forEach(e=>{try{e.close()}catch{}}),this.notificationQueue=[]}resetUnreadCount(){this.unreadCount=0,this.updateTitle()}getStatus(){return{permission:this.permission,isTabActive:this.isTabActive,unreadCount:this.unreadCount,isSecureContext:this.isSecureContext,queueSize:this.notificationQueue.length}}},Yt=class{constructor(){this.notificationManager=new wt({maxQueueSize:5,rateLimitMs:2e3,trustedOrigins:[window.Line truncated
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function dn(n,e){return on(n)||cn(n,e)||un(n,e)||ln()}function un(n,e){if(n){if(typeof n=="string")return gi(n,e);var t={}.toString.call(n).slice(8,-1);return t==="Object"&&n.constructor&&(t=n.constructor.name),t==="Map"||t==="Set"?Array.from(n):t==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?gi(n,e):void 0}}var Ri=Object.entries,mi=Object.setPrototypeOf,hn=Object.isFrozen,fn=Object.getPrototypeOf,pn=Object.getOwnPropertyDescriptor,ne=Object.freeze,ae=Object.seal,rt=Object.create,Mi=typeof Reflect<"u"&&Reflect,_r=Mi.apply,br=Mi.construct;ne||(ne=function(e){return e});ae||(ae=function(e){return e});_r||(_r=function(e,t){for(var r=arguments.length,i=new Array(r>2?r-2:0),s=2;s<r;s++)i[s-2]=arguments[s];return e.apply(t,i)});br||(br=function(e){for(var t=arguments.length,r=new Array(t>1?t-1:0),i=1;i<t;i++)r[i-1]=arguments[i];return new e(...r)});var ht=te(Array.prototype.forEach),yn=te(Array.prototype.lastIndexOf),Si=te(Array.prototype.pop),tt=te(Array.prototype.push),gn=te(Array.prototype.splice),Fe=Array.isArray,yt=te(String.prototype.toLowerCase),pr=te(String.prototype.toString),_i=te(String.prototype.match),ft=te(String.prototype.replace),bi=te(String.prototype.indexOf),mn=te(String.prototype.trim),Sn=te(Number.prototype.toString),_n=te(Boolean.prototype.toString),wi=typeof BigInt>"u"?null:te(BigInt.prototype.toString),Ei=typeof Symbol>"u"?null:te(Symbol.prototype.toString),he=te(Object.prototype.hasOwnProperty),pt=te(Object.prototype.toString),se=te(RegExp.prototype.test),$e=bn(TypeError);function te(n){return function(e){e instanceof RegExp&&(e.lastIndex=0);for(var t=arguments.length,r=new Array(t>1?t-1:0),i=1;i<t;i++)r[i-1]=arguments[i];return _r(n,e,r)}}function bn(n){return function(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];return br(n,t)}}function V(n,e){let t=arguments.length>2&&arguments[2]!==void 0?arguments[2]:yt;if(mi&&mi(n,null),!Fe(e))return n;let r=e.length;for(;r--;){let i=e[r];if(typeof i=="string"){let s=t(i);s!==i&&(hn(e)||(e[r]=s),i=s)}n[i]=!0}return n}function wn(n){for(let e=0;e<n.length;e++)he(n,e)||(n[e]=null);return n}function ue(n){let e=rt(null);for(let r of Ri(n)){var t=dn(r,2);let i=t[0],s=t[1];he(n,i)&&(Fe(s)?e[i]=wn(s):s&&typeof s=="object"&&s.constructor===Object?e[i]=ue(s):e[i]=s)}return e}function En(n){switch(typeof n){case"string":return n;case"number":return Sn(n);case"boolean":return _n(n);case"bigint":return wi?wi(n):"0";case"symbol":return Ei?Ei(n):"Symbol()";case"undefined":return pt(n);case"function":case"object":{if(n===null)return pt(n);let e=n,t=Ae(e,"toString");if(typeof t=="function"){let r=t(e);return typeof r=="string"?r:pt(r)}return pt(n)}default:return pt(n)}}function Ae(n,e){for(;n!==null;){let r=pn(n,e);if(r){if(r.get)return te(r.get);if(typeof r.value=="function")return te(r.value)}n=fn(n)}function t(){return null}return t}function vn(n){try{return se(n,""),!0}catch{return!1}}var vi=ne(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","search","section","select","shadow","slot","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),yr=ne(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","enterkeyhint","exportparts","filter","font","g","glyph","glyphref","hkern","image","inputmode","line","lineargradient","marker","mask","metadata","mpath","part","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),gr=ne(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),Tn=ne(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor" Line truncated In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function gs(s,e){return fs(s)||ps(s,e)||ms(s,e)||ys()}function ms(s,e){if(s){if(typeof s=="string")return Ei(s,e);var t={}.toString.call(s).slice(8,-1);return t==="Object"&&s.constructor&&(t=s.constructor.name),t==="Map"||t==="Set"?Array.from(s):t==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?Ei(s,e):void 0}}var Ki=Object.entries,vi=Object.setPrototypeOf,Ss=Object.isFrozen,bs=Object.getPrototypeOf,_s=Object.getOwnPropertyDescriptor,ce=Object.freeze,le=Object.seal,rt=Object.create,Ni=typeof Reflect<"u"&&Reflect,wr=Ni.apply,Er=Ni.construct;ce||(ce=function(e){return e});le||(le=function(e){return e});wr||(wr=function(e,t){for(var r=arguments.length,i=new Array(r>2?r-2:0),n=2;n<r;n++)i[n-2]=arguments[n];return e.apply(t,i)});Er||(Er=function(e){for(var t=arguments.length,r=new Array(t>1?t-1:0),i=1;i<t;i++)r[i-1]=arguments[i];return new e(...r)});var ht=se(Array.prototype.forEach),ws=se(Array.prototype.lastIndexOf),Ti=se(Array.prototype.pop),tt=se(Array.prototype.push),Es=se(Array.prototype.splice),Fe=Array.isArray,yt=se(String.prototype.toLowerCase),gr=se(String.prototype.toString),Ci=se(String.prototype.match),ft=se(String.prototype.replace),ki=se(String.prototype.indexOf),vs=se(String.prototype.trim),Ts=se(Number.prototype.toString),Cs=se(Boolean.prototype.toString),Ai=typeof BigInt>"u"?null:se(BigInt.prototype.toString),xi=typeof Symbol>"u"?null:se(Symbol.prototype.toString),ye=se(Object.prototype.hasOwnProperty),pt=se(Object.prototype.toString),oe=se(RegExp.prototype.test),$e=ks(TypeError);function se(s){return function(e){e instanceof RegExp&&(e.lastIndex=0);for(var t=arguments.length,r=new Array(t>1?t-1:0),i=1;i<t;i++)r[i-1]=arguments[i];return wr(s,e,r)}}function ks(s){return function(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];return Er(s,t)}}function H(s,e){let t=arguments.length>2&&arguments[2]!==void 0?arguments[2]:yt;if(vi&&vi(s,null),!Fe(e))return s;let r=e.length;for(;r--;){let i=e[r];if(typeof i=="string"){let n=t(i);n!==i&&(Ss(e)||(e[r]=n),i=n)}s[i]=!0}return s}function As(s){for(let e=0;e<s.length;e++)ye(s,e)||(s[e]=null);return s}function pe(s){let e=rt(null);for(let r of Ki(s)){var t=gs(r,2);let i=t[0],n=t[1];ye(s,i)&&(Fe(n)?e[i]=As(n):n&&typeof n=="object"&&n.constructor===Object?e[i]=pe(n):e[i]=n)}return e}function xs(s){switch(typeof s){case"string":return s;case"number":return Ts(s);case"boolean":return Cs(s);case"bigint":return Ai?Ai(s):"0";case"symbol":return xi?xi(s):"Symbol()";case"undefined":return pt(s);case"function":case"object":{if(s===null)return pt(s);let e=s,t=Ae(e,"toString");if(typeof t=="function"){let r=t(e);return typeof r=="string"?r:pt(r)}return pt(s)}default:return pt(s)}}function Ae(s,e){for(;s!==null;){let r=_s(s,e);if(r){if(r.get)return se(r.get);if(typeof r.value=="function")return se(r.value)}s=bs(s)}function t(){return null}return t}function Is(s){try{return oe(s,""),!0}catch{return!1}}var Ii=ce(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","search","section","select","shadow","slot","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),mr=ce(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","enterkeyhint","exportparts","filter","font","g","glyph","glyphref","hkern","image","inputmode","line","lineargradient","marker","mask","metadata","mpath","part","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),Sr=ce(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),Rs=ce(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor" Line truncated
`+ee),Le&&(ee=kt(ee)),b&&vt?R(ee):ee},e.setConfig=function(){let C=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};dr(C),er=!0},e.clearConfig=function(){Ze=null,er=!1,b=x,A=""},e.isValidAttribute=function(C,l,y){Ze||dr({});let E=J(C),L=J(l);return di(E,L,y)},e.addHook=function(C,l){typeof l=="function"&&tt(I[C],l)},e.removeHook=function(C,l){if(l!==void 0){let y=yn(I[C],l);return y===-1?void 0:gn(I[C],y,1)[0]}return Si(I[C])},e.removeHooks=function(C){I[C]=[]},e.removeAllHooks=function(){I=Ii()},e}var Di=Li();var Rt=class n{static _keyMetadata=new WeakMap;static _messageSanitizer=null;static sortObjectKeys(e){if(typeof e!="object"||e===null)return e;if(Array.isArray(e))return e.map(n.sortObjectKeys);let t={};return Object.keys(e).sort().forEach(r=>{t[r]=n.sortObjectKeys(e[r])}),t}static assertCryptoKey(e,t=null,r=[]){if(!(e instanceof CryptoKey))throw new Error("Expected CryptoKey");if(t&&e.algorithm?.name!==t)throw new Error(`Expected algorithm ${t}, got ${e.algorithm?.name}`);for(let i of r)if(!e.usages||!e.usages.includes(i))throw new Error(`Missing required key usage: ${i}`)}static arrayBufferToBase64(e){let t="",r=new Uint8Array(e),i=r.byteLength;for(let s=0;s<i;s++)t+=String.fromCharCode(r[s]);return btoa(t)}static base64ToArrayBuffer(e){try{if(typeof e!="string"||!e)throw new Error("Invalid base64 input: must be a non-empty string");let t=e.trim();if(!/^[A-Za-z0-9+/]*={0,2}$/.test(t))throw new Error("Invalid base64 format");if(t==="")return new ArrayBuffer(0);let r=atob(t),i=r.length,s=new Uint8Array(i);for(let a=0;a<i;a++)s[a]=r.charCodeAt(a);return s.buffer}catch(t){throw console.error("Base64 to ArrayBuffer conversion failed:",t.message),new Error(`Base64 conversion error: ${t.message}`)}}static hexToUint8Array(e){try{if(!e||typeof e!="string")throw new Error("Invalid hex string input: must be a non-empty string");let t=e.replace(/:/g,"").replace(/\s/g,"");if(!/^[0-9a-fA-F]*$/.test(t))throw new Error("Invalid hex format: contains non-hex characters");if(t.length%2!==0)throw new Error("Invalid hex format: odd length");let r=new Uint8Array(t.length/2);for(let i=0;i<t.length;i+=2)r[i/2]=parseInt(t.substr(i,2),16);return r}catch(t){throw console.error("Hex to Uint8Array conversion failed:",t.message),new Error(`Hex conversion error: ${t.message}`)}}static zeroizeBuffer(e){try{if(!e)return;let t=e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):null;if(!t||t.length===0)return;crypto.getRandomValues(t),t.fill(0)}catch{}}static async encryptData(e,t){try{let r=typeof e=="string"?e:JSON.stringify(e),i=crypto.getRandomValues(new Uint8Array(16)),s=new TextEncoder,a=s.encode(t),o=await crypto.subtle.importKey("raw",a,{name:"PBKDF2"},!1,["deriveKey"]),c=await crypto.subtle.deriveKey({name:"PBKDF2",salt:i,iterations:31e4,hash:"SHA-256"},o,{name:"AES-GCM",length:256},!1,["encrypt"]),d=crypto.getRandomValues(new Uint8Array(12)),u=s.encode(r),h=await crypto.subtle.encrypt({name:"AES-GCM",iv:d},c,u),m={version:"1.0",salt:Array.from(i),iv:Array.from(d),data:Array.from(new Uint8Array(h)),timestamp:Date.now()},p=JSON.stringify(m);return n.arrayBufferToBase64(new TextEncoder().encode(p).buffer)}catch(r){throw console.error("Encryption failed:",r.message),new Error(`Encryption error: ${r.message}`)}}static async decryptData(e,t){try{let r=n.base64ToArrayBuffer(e),i=new TextDecoder().decode(r),s=JSON.parse(i);if(!s.version||!s.salt||!s.iv||!s.data)throw new Error("Invalid encrypted data format");let a=new Uint8Array(s.salt),o=new Uint8Array(s.iv),c=new Uint8Array(s.data),u=new TextEncoder().encode(t),h=await crypto.subtle.importKey("raw",u,{name:"PBKDF2"},!1,["deriveKey"]),m=await crypto.subtle.deriveKey({name:"PBKDF2",salt:a,iterations:31e4,hash:"SHA-256"},h,{name:"AES-GCM",length:256},!1,["decrypt"]),p=await crypto.subtle.decrypt({name:"AES-GCM",iv:o},m,c),S=new TextDecoder().decode(p);try{return JSON.parse(S)}catch{return S}}catch(r){throw console.error("Decryption failed:",r.message),new Error(`Decryption error: ${r.message}`)}}static generateSecurePassword(){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?",t=e.length,r=32,i="";for(let s=0;s<r;s++){let a;do a=crypto.getRandomValues(new Uint32Array(1))[0];while(a>=4294967296-4294967296%t);i+=e[a%t]}return i}static async calculateSecurityLevel(e){let t=0,r=100,i={};try{if(!e||!e.securityFeatures)return console.warn("Security manager not fully initialized, using fallback calculation"),{level:"INITIALIZING",score:0,color:"gray",verificationResults:{},timestamp:Date.now(),details:"Security system initializing...",isRealData:!1};let s="full",a=!1;try{let h=await n.verifyEncryption(e);h.passed?(t+=20,i.verifyEncryption={passed:!0,details:h.details,points:20}):i.verifyEncryption={passed:!1,details:h.details,points:0}}catch(h){i.verifyEncryption={passed:!1,details:`Encryption check failed: ${h.message}`,points:0}}try{let h=await n.verifyECDHKeyExchange(e);h.passed?(t+=15,i.verifyECDHKeyExchange={pa Line truncated `+ne),Le&&(ne=At(ne)),_&&Tt?R(ne):ne},e.setConfig=function(){let C=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};hr(C),rr=!0},e.clearConfig=function(){Ze=null,rr=!1,_=x,A=""},e.isValidAttribute=function(C,l,y){Ze||hr({});let v=re(C),L=re(l);return gi(v,L,y)},e.addHook=function(C,l){typeof l=="function"&&tt(I[C],l)},e.removeHook=function(C,l){if(l!==void 0){let y=ws(I[C],l);return y===-1?void 0:Es(I[C],y,1)[0]}return Ti(I[C])},e.removeHooks=function(C){I[C]=[]},e.removeAllHooks=function(){I=Fi()},e}var Ui=Oi();var Mt=class s{static _keyMetadata=new WeakMap;static _messageSanitizer=null;static sortObjectKeys(e){if(typeof e!="object"||e===null)return e;if(Array.isArray(e))return e.map(s.sortObjectKeys);let t={};return Object.keys(e).sort().forEach(r=>{t[r]=s.sortObjectKeys(e[r])}),t}static assertCryptoKey(e,t=null,r=[]){if(!(e instanceof CryptoKey))throw new Error("Expected CryptoKey");if(t&&e.algorithm?.name!==t)throw new Error(`Expected algorithm ${t}, got ${e.algorithm?.name}`);for(let i of r)if(!e.usages||!e.usages.includes(i))throw new Error(`Missing required key usage: ${i}`)}static arrayBufferToBase64(e){let t="",r=new Uint8Array(e),i=r.byteLength;for(let n=0;n<i;n++)t+=String.fromCharCode(r[n]);return btoa(t)}static base64ToArrayBuffer(e){try{if(typeof e!="string"||!e)throw new Error("Invalid base64 input: must be a non-empty string");let t=e.trim();if(!/^[A-Za-z0-9+/]*={0,2}$/.test(t))throw new Error("Invalid base64 format");if(t==="")return new ArrayBuffer(0);let r=atob(t),i=r.length,n=new Uint8Array(i);for(let a=0;a<i;a++)n[a]=r.charCodeAt(a);return n.buffer}catch(t){throw console.error("Base64 to ArrayBuffer conversion failed:",t.message),new Error(`Base64 conversion error: ${t.message}`)}}static hexToUint8Array(e){try{if(!e||typeof e!="string")throw new Error("Invalid hex string input: must be a non-empty string");let t=e.replace(/:/g,"").replace(/\s/g,"");if(!/^[0-9a-fA-F]*$/.test(t))throw new Error("Invalid hex format: contains non-hex characters");if(t.length%2!==0)throw new Error("Invalid hex format: odd length");let r=new Uint8Array(t.length/2);for(let i=0;i<t.length;i+=2)r[i/2]=parseInt(t.substr(i,2),16);return r}catch(t){throw console.error("Hex to Uint8Array conversion failed:",t.message),new Error(`Hex conversion error: ${t.message}`)}}static zeroizeBuffer(e){try{if(!e)return;let t=e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):null;if(!t||t.length===0)return;crypto.getRandomValues(t),t.fill(0)}catch{}}static async encryptData(e,t){try{let r=typeof e=="string"?e:JSON.stringify(e),i=crypto.getRandomValues(new Uint8Array(16)),n=new TextEncoder,a=n.encode(t),o=await crypto.subtle.importKey("raw",a,{name:"PBKDF2"},!1,["deriveKey"]),c=await crypto.subtle.deriveKey({name:"PBKDF2",salt:i,iterations:31e4,hash:"SHA-256"},o,{name:"AES-GCM",length:256},!1,["encrypt"]),d=crypto.getRandomValues(new Uint8Array(12)),u=n.encode(r),h=await crypto.subtle.encrypt({name:"AES-GCM",iv:d},c,u),m={version:"1.0",salt:Array.from(i),iv:Array.from(d),data:Array.from(new Uint8Array(h)),timestamp:Date.now()},p=JSON.stringify(m);return s.arrayBufferToBase64(new TextEncoder().encode(p).buffer)}catch(r){throw console.error("Encryption failed:",r.message),new Error(`Encryption error: ${r.message}`)}}static async decryptData(e,t){try{let r=s.base64ToArrayBuffer(e),i=new TextDecoder().decode(r),n=JSON.parse(i);if(!n.version||!n.salt||!n.iv||!n.data)throw new Error("Invalid encrypted data format");let a=new Uint8Array(n.salt),o=new Uint8Array(n.iv),c=new Uint8Array(n.data),u=new TextEncoder().encode(t),h=await crypto.subtle.importKey("raw",u,{name:"PBKDF2"},!1,["deriveKey"]),m=await crypto.subtle.deriveKey({name:"PBKDF2",salt:a,iterations:31e4,hash:"SHA-256"},h,{name:"AES-GCM",length:256},!1,["decrypt"]),p=await crypto.subtle.decrypt({name:"AES-GCM",iv:o},m,c),S=new TextDecoder().decode(p);try{return JSON.parse(S)}catch{return S}}catch(r){throw console.error("Decryption failed:",r.message),new Error(`Decryption error: ${r.message}`)}}static generateSecurePassword(){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?",t=e.length,r=32,i="";for(let n=0;n<r;n++){let a;do a=crypto.getRandomValues(new Uint32Array(1))[0];while(a>=4294967296-4294967296%t);i+=e[a%t]}return i}static async calculateSecurityLevel(e){let t=0,r=100,i={};try{if(!e||!e.securityFeatures)return console.warn("Security manager not fully initialized, using fallback calculation"),{level:"INITIALIZING",score:0,color:"gray",verificationResults:{},timestamp:Date.now(),details:"Security system initializing...",isRealData:!1};let n="full",a=!1;try{let h=await s.verifyEncryption(e);h.passed?(t+=20,i.verifyEncryption={passed:!0,details:h.details,points:20}):i.verifyEncryption={passed:!1,details:h.details,points:0}}catch(h){i.verifyEncryption={passed:!1,details:`Encryption check failed: ${h.message}`,points:0}}try{let h=await s.verifyECDHKeyExchange(e);h.passed?(t+=15,i.verifyECDHKeyExchange={ Line truncated
Please wait for real-time cryptographic verification to complete.`,"sec.verificationUnavailable":"Security verification not available","chat.placeholder":"Type an encrypted message\u2026","chat.send":"Send message","chat.sendFiles":"Send files","chat.hideFiles":"Hide files","chat.recordVoice":"Record voice message","chat.sendVoice":"Send voice message","chat.discard":"Discard","chat.codeBlock":"Send as a code block (expands the input)","chat.codeHint":"Code snippet \xB7 formatting preserved \xB7 \u2318\u21B5 to send","chat.viewOnce":"View once","chat.viewOnceTitle":"View once - vanishes after the peer reads it","chat.viewOncePrefix":"View once \xB7 ","chat.viewOnceTap":"View once \xB7 tap to reveal","chat.viewedOnce":"Viewed once","chat.disappearing":"Disappearing message - deletes on both sides","chat.disappearAfter":"Disappear after","chat.visibleFor":"Visible for","chat.timerPrefix":"Timer \xB7 ","chat.expired":"This message has expired","chat.deleteForEveryone":"Delete for everyone","chat.sending":"Sending","chat.delivered":"Delivered","chat.notSent":"Not sent","chat.notSentReason":"Not sent - the secure channel is not ready. Reconnect to continue.","chat.encrypted":"Encrypted","chat.decrypted":"Decrypted","chat.encryptedOnDevice":"Encrypted on your device","chat.uploading":"Uploading","chat.downloading":"Downloading","chat.transferring":"Transferring\u2026","chat.copied":"Copied!","chat.collapse":"Collapse","chat.closeMenu":"Close menu","chat.newChat":"New chat","chat.newGroup":"New group","chat.createGroup":"Create a group","chat.groupChats":"Group chats","chat.secureChat":"Secure chat","chat.nameThis":"Name this chat","chat.rename":"Rename chat (local only)","chat.renameHint":"Double-click to rename","chat.localLabel":"Local label \xB7 stored only on this device","chat.setStatus":"Set your status","chat.yourStatusPrefix":"Your status - ","chat.meshHint":"Up to 8 peers \xB7 P2P mesh","chat.offline":"Offline","chat.noNetwork":"No network \xB7 reconnecting","chat.startVoiceCall":"Start encrypted voice call","chat.startVideoCall":"Start encrypted video call","chat.verifyForCalls":"Verify the session to enable calls","chat.e2eeNote":"Every message is end-to-end encrypted on your device before it leaves.","chat.peersOnly":"Sent end-to-end to connected peers only - never stored on a server.","flow.invitationCreated":"Secure invitation created","flow.invitationCreatedBang":"Secure invitation created and encrypted!","flow.responseCreated":"Secure response created","flow.responseCreatedBang":"Secure response created!","flow.sendInvitation":"Send the invitation code to your interlocutor via a secure channel (voice call, SMS, etc.).","flow.sendResponse":"Send the response code to the initiator via a secure channel or let them scan the QR code below.","flow.sendEncryptedCode":"Send the encrypted code","flow.sendTheResponse":"Send the response","flow.pasteOrWrite":"Paste or write code\u2026","flow.processingInvitation":"Processing the secure invitation...","flow.processingResponse":"Processing the secure response...","flow.finalizing":"Finalizing the secure connection...","flow.channelEstablished":"Secure channel established","flow.channelReady":"Secure channel is ready","flow.restoring":"Restoring connection\u2026","flow.invitationCaptured":"Invitation captured.","err.needInvitation":"You need to insert the invitation code from your interlocutor.","err.needResponse":"You need to insert the response code from your interlocutor.","err.responseFormat":"Invalid response format - please check the code","err.responseNoKey":"Invalid response code - missing or corrupted cryptographic key. Please check the code and try again.","err.responseNoSignKey":"Invalid response code - missing signature verification key. Please check the code and try again.","err.responseOutdated":"Response data is outdated - please use a fresh invitation","err.setupError":"Connection setup error","err.notReady":"Connection not ready","err.securityBreach":"Security breach detected - connection rejected","err.securityValidation":"Security validation failed - possible attack detected","err.retiredQr":"This QR code uses a retired format that could not transfer the invitation. Ask your peer to generate a new one, or use copy/paste.","err.compressedQrNote":"Compressed QR may omit SDP for brevity. Use copy/paste if connection fails.","err.inviteNotConnected":"The invitation could not be sent. That chat is not connected right now.","err.inviteNotConnectedRetry":"The invitation could not be sent. That chat is not connected right now - reopen it and try again.","err.nobodyAccepted":"Nobody accepted the invitation. The group is unchanged.","err.noAudio":"No audio captured - check microphone permission and try again.","err.voiceNeedsConnection":"Voice message needs an active secure connection. Reconnect and try again.","err.voiceRestoring":"Restoring the connection - try sending the voice message again in a moment.","err.fileTooLarge":"File too large","sas.sa Line truncated Please wait for real-time cryptographic verification to complete.`,"sec.verificationUnavailable":"Security verification not available","chat.placeholder":"Type an encrypted message\u2026","chat.send":"Send message","chat.sendFiles":"Send files","chat.hideFiles":"Hide files","chat.recordVoice":"Record voice message","chat.sendVoice":"Send voice message","chat.discard":"Discard","chat.codeBlock":"Send as a code block (expands the input)","chat.codeHint":"Code snippet \xB7 formatting preserved \xB7 \u2318\u21B5 to send","chat.viewOnce":"View once","chat.viewOnceTitle":"View once - vanishes after the peer reads it","chat.viewOncePrefix":"View once \xB7 ","chat.viewOnceTap":"View once \xB7 tap to reveal","chat.viewedOnce":"Viewed once","chat.disappearing":"Disappearing message - deletes on both sides","chat.disappearAfter":"Disappear after","chat.visibleFor":"Visible for","chat.timerPrefix":"Timer \xB7 ","chat.expired":"This message has expired","chat.deleteForEveryone":"Delete for everyone","chat.sending":"Sending","chat.delivered":"Delivered","chat.notSent":"Not sent","chat.notSentReason":"Not sent - the secure channel is not ready. Reconnect to continue.","chat.encrypted":"Encrypted","chat.decrypted":"Decrypted","chat.encryptedOnDevice":"Encrypted on your device","chat.uploading":"Uploading","chat.downloading":"Downloading","chat.transferring":"Transferring\u2026","chat.copied":"Copied!","chat.collapse":"Collapse","chat.closeMenu":"Close menu","chat.newChat":"New chat","chat.newGroup":"New group","chat.createGroup":"Create a group","chat.groupChats":"Group chats","chat.secureChat":"Secure chat","chat.nameThis":"Name this chat","chat.rename":"Rename chat (local only)","chat.renameHint":"Double-click to rename","chat.localLabel":"Local label \xB7 stored only on this device","chat.setStatus":"Set your status","chat.yourStatusPrefix":"Your status - ","chat.meshHint":"Up to 8 peers \xB7 P2P mesh","chat.offline":"Offline","chat.noNetwork":"No network \xB7 reconnecting","chat.startVoiceCall":"Start encrypted voice call","chat.startVideoCall":"Start encrypted video call","chat.verifyForCalls":"Verify the session to enable calls","chat.e2eeNote":"Every message is end-to-end encrypted on your device before it leaves.","chat.peersOnly":"Sent end-to-end to connected peers only - never stored on a server.","flow.invitationCreated":"Secure invitation created","flow.invitationCreatedBang":"Secure invitation created and encrypted!","flow.responseCreated":"Secure response created","flow.responseCreatedBang":"Secure response created!","flow.sendInvitation":"Send the invitation code to your interlocutor via a secure channel (voice call, SMS, etc.).","flow.sendResponse":"Send the response code to the initiator via a secure channel or let them scan the QR code below.","flow.sendEncryptedCode":"Send the encrypted code","flow.sendTheResponse":"Send the response","flow.pasteOrWrite":"Paste or write code\u2026","flow.processingInvitation":"Processing the secure invitation...","flow.processingResponse":"Processing the secure response...","flow.finalizing":"Finalizing the secure connection...","flow.channelEstablished":"Secure channel established","flow.channelReady":"Secure channel is ready","flow.restoring":"Restoring connection\u2026","flow.invitationCaptured":"Invitation captured.","err.needInvitation":"You need to insert the invitation code from your interlocutor.","err.needResponse":"You need to insert the response code from your interlocutor.","err.responseFormat":"Invalid response format - please check the code","err.responseNoKey":"Invalid response code - missing or corrupted cryptographic key. Please check the code and try again.","err.responseNoSignKey":"Invalid response code - missing signature verification key. Please check the code and try again.","err.responseOutdated":"Response data is outdated - please use a fresh invitation","err.setupError":"Connection setup error","err.notReady":"Connection not ready","err.securityBreach":"Security breach detected - connection rejected","err.securityValidation":"Security validation failed - possible attack detected","err.retiredQr":"This QR code uses a retired format that could not transfer the invitation. Ask your peer to generate a new one, or use copy/paste.","err.compressedQrNote":"Compressed QR may omit SDP for brevity. Use copy/paste if connection fails.","err.inviteNotConnected":"The invitation could not be sent. That chat is not connected right now.","err.inviteNotConnectedRetry":"The invitation could not be sent. That chat is not connected right now - reopen it and try again.","err.nobodyAccepted":"Nobody accepted the invitation. The group is unchanged.","err.noAudio":"No audio captured - check microphone permission and try again.","err.voiceNeedsConnection":"Voice message needs an active secure connection. Reconnect and try again.","err.voiceRestoring":"Restoring the connection - try sending the voice message again in a moment.","err.fileTooLarge":"File too large","sas.sa Line truncated
`)!==-1?`\r `)!==-1?`\r
`:` `:`
`}function Bi(n){let e=Xn(n),t=n.split(/\r\n|\n/),r=[],i=[],s=null;for(let a of t)a.startsWith("m=")?(s={lines:[a]},i.push(s)):s?s.lines.push(a):r.push(a);return{eol:e,session:r,media:i}}function Hi(n){let e=[...n.session];for(let t of n.media)e.push(...t.lines);return e.join(n.eol)}function $i(n){let e=n.lines[0].match(/^m=(\w+)/);return e?e[1]:null}function Qn(n,e){let t=new RegExp("^a=rtpmap:(\\d+)\\s+"+e+"\\/","i"),r=[];for(let i of n.lines){let s=i.match(t);s&&r.push(s[1])}return r}function Jn(n){let e=new Map;for(let t of n.split(";")){let r=t.trim();if(!r)continue;let i=r.indexOf("=");i===-1?e.set(r,void 0):e.set(r.slice(0,i).trim(),r.slice(i+1).trim())}return e}function Vi(n){let e=[];for(let[t,r]of n)e.push(r===void 0?t:`${t}=${r}`);return e.join(";")}function Zn(n,e,t){let r=n.lines.findIndex(o=>o.startsWith(`a=fmtp:${e} `)||o===`a=fmtp:${e}`);if(r!==-1){let o=n.lines[r].slice(`a=fmtp:${e} `.length),c=Jn(o);for(let[d,u]of Object.entries(t))c.set(d,String(u));n.lines[r]=`a=fmtp:${e} ${Vi(c)}`;return}let i=new Map;for(let[o,c]of Object.entries(t))i.set(o,String(c));let s=`a=fmtp:${e} ${Vi(i)}`,a=n.lines.findIndex(o=>o.startsWith(`a=rtpmap:${e} `));a!==-1?n.lines.splice(a+1,0,s):n.lines.push(s)}function vr(n,e){if(!n||typeof n!="string")return n;let t=Bi(n),r=!1;for(let i of t.media)if($i(i)==="audio")for(let s of Qn(i,"opus"))Zn(i,s,e),r=!0;return r?Hi(t):n}var ea=/^(rtx|red|ulpfec|flexfec-03|telephone-event|CN)$/i;function ta(n){let e=[];for(let t of n.lines){let r=t.match(/^a=rtpmap:(\d+)\s+([^/]+)\//);r&&!ea.test(r[2])&&e.push(r[1])}return e}function ra(n,e){for(let t of ta(n))for(let r of e){let i=`a=rtcp-fb:${t} ${r}`;if(n.lines.includes(i))continue;let s=-1;for(let a=0;a<n.lines.length;a++){let o=n.lines[a];(o.startsWith(`a=rtpmap:${t} `)||o.startsWith(`a=fmtp:${t} `)||o.startsWith(`a=rtcp-fb:${t} `))&&(s=a)}s===-1&&(s=n.lines.length-1),n.lines.splice(s+1,0,i)}}function ia(n,e){if(n.lines.some(i=>i.startsWith("a=extmap:")&&i.includes(e)))return;let t=0,r=-1;for(let i=0;i<n.lines.length;i++){let s=n.lines[i].match(/^a=extmap:(\d+)/);s&&(t=Math.max(t,Number(s[1])),r=i)}r===-1&&(r=n.lines.findIndex(i=>i.startsWith("a=mid:")),r===-1&&(r=n.lines.length-1)),n.lines.splice(r+1,0,`a=extmap:${t+1} ${e}`)}function Gi(n,e){if(!n||typeof n!="string"||!e)return n;let t=Bi(n),r=!1;for(let i of t.media){let s=$i(i),a=s==="video"?e.video:s==="audio"?e.audio:null;a&&(Array.isArray(a.rtcpFb)&&(ra(i,a.rtcpFb),r=!0),a.twcc&&e.twccUri&&(ia(i,e.twccUri),r=!0))}return r?Hi(t):n}function qi(n){try{if(Ue||!n||typeof n.setCodecPreferences!="function"||!nt.preferRed)return!1;let e=typeof RTCRtpSender<"u"&&RTCRtpSender.getCapabilities?RTCRtpSender.getCapabilities("audio"):null;if(!e||!Array.isArray(e.codecs))return!1;let t=o=>/red$/i.test(o.mimeType),r=o=>/opus$/i.test(o.mimeType);if(!e.codecs.some(t))return!1;let i=e.codecs.filter(t),s=e.codecs.filter(r),a=e.codecs.filter(o=>!t(o)&&!r(o));return n.setCodecPreferences([...i,...s,...a]),!0}catch{return!1}}async function ji(n,e={}){try{if(Ue||!n||typeof n.getParameters!="function")return!1;let t={...nt.sender,...e},r=n.getParameters();(!r.encodings||r.encodings.length===0)&&(r.encodings=[{}]);for(let i of r.encodings)i.maxBitrate=t.maxBitrate,i.priority=t.priority,i.networkPriority=t.networkPriority;return await n.setParameters(r),!0}catch{return!1}}var Wi={VP9:0,AV1:1,H264:2,VP8:3};function Yi(n){return(String(n||"").split("/")[1]||"").toUpperCase()}function sa(n){let e=t=>{let r=Yi(t.mimeType);return Object.prototype.hasOwnProperty.call(Wi,r)?Wi[r]:99};return n.map((t,r)=>({c:t,i:r})).sort((t,r)=>e(t.c)-e(r.c)||t.i-r.i).map(t=>t.c)}function Xi(n){if(!n||!Array.isArray(n.codecs))return null;let e=new Set(n.codecs.map(t=>Yi(t.mimeType)));for(let t of Re.codecPreferenceOrder)if(e.has(t))return t;return null}function Qi(){return typeof RTCRtpSender<"u"&&RTCRtpSender.getCapabilities?RTCRtpSender.getCapabilities("video"):null}function Ji(n){try{if(Ue||!n||typeof n.setCodecPreferences!="function")return!1;let e=Qi();return!e||!Array.isArray(e.codecs)?!1:(n.setCodecPreferences(sa(e.codecs)),Xi(e))}catch{return!1}}function na(n){return n==="VP9"?{scalabilityMode:Re.vp9.preferredScalabilityMode,maxBitrate:15e5,degradationPreference:Re.vp9.degradationPreference}:n==="AV1"?{scalabilityMode:Re.av1.scalabilityMode,maxBitrate:Re.av1.maxBitrate,degradationPreference:Re.av1.degradationPreference}:{scalabilityMode:void 0,maxBitrate:15e5,degradationPreference:"balanced"}}async function Zi(n,e={}){try{if(Ue||!n||typeof n.getParameters!="function")return!1;let t=Xi(Qi())||"VP8",r={...na(t),...e},i=n.getParameters();(!i.encodings||i.encodings.length===0)&&(i.encodings=[{}]);let s=i.encodings.length>1;if(s)for(let a of i.encodings)a.networkPriority=Re.networkPriority;else{let a=i.encodings[0];a.maxBitrate=r.maxBitrate,a.networkPriority=Re.networkPriority,r.scalabilityMode&&(a.scalabilityMode=r.scalabilityMode)}r.degradationPreference&&(i.degradationPreference=r.degradationPreference);try{retu Line truncated `}function Wi(s){let e=ta(s),t=s.split(/\r\n|\n/),r=[],i=[],n=null;for(let a of t)a.startsWith("m=")?(n={lines:[a]},i.push(n)):n?n.lines.push(a):r.push(a);return{eol:e,session:r,media:i}}function Yi(s){let e=[...s.session];for(let t of s.media)e.push(...t.lines);return e.join(s.eol)}function Xi(s){let e=s.lines[0].match(/^m=(\w+)/);return e?e[1]:null}function ra(s,e){let t=new RegExp("^a=rtpmap:(\\d+)\\s+"+e+"\\/","i"),r=[];for(let i of s.lines){let n=i.match(t);n&&r.push(n[1])}return r}function ia(s){let e=new Map;for(let t of s.split(";")){let r=t.trim();if(!r)continue;let i=r.indexOf("=");i===-1?e.set(r,void 0):e.set(r.slice(0,i).trim(),r.slice(i+1).trim())}return e}function ji(s){let e=[];for(let[t,r]of s)e.push(r===void 0?t:`${t}=${r}`);return e.join(";")}function na(s,e,t){let r=s.lines.findIndex(o=>o.startsWith(`a=fmtp:${e} `)||o===`a=fmtp:${e}`);if(r!==-1){let o=s.lines[r].slice(`a=fmtp:${e} `.length),c=ia(o);for(let[d,u]of Object.entries(t))c.set(d,String(u));s.lines[r]=`a=fmtp:${e} ${ji(c)}`;return}let i=new Map;for(let[o,c]of Object.entries(t))i.set(o,String(c));let n=`a=fmtp:${e} ${ji(i)}`,a=s.lines.findIndex(o=>o.startsWith(`a=rtpmap:${e} `));a!==-1?s.lines.splice(a+1,0,n):s.lines.push(n)}function Cr(s,e){if(!s||typeof s!="string")return s;let t=Wi(s),r=!1;for(let i of t.media)if(Xi(i)==="audio")for(let n of ra(i,"opus"))na(i,n,e),r=!0;return r?Yi(t):s}var sa=/^(rtx|red|ulpfec|flexfec-03|telephone-event|CN)$/i;function aa(s){let e=[];for(let t of s.lines){let r=t.match(/^a=rtpmap:(\d+)\s+([^/]+)\//);r&&!sa.test(r[2])&&e.push(r[1])}return e}function oa(s,e){for(let t of aa(s))for(let r of e){let i=`a=rtcp-fb:${t} ${r}`;if(s.lines.includes(i))continue;let n=-1;for(let a=0;a<s.lines.length;a++){let o=s.lines[a];(o.startsWith(`a=rtpmap:${t} `)||o.startsWith(`a=fmtp:${t} `)||o.startsWith(`a=rtcp-fb:${t} `))&&(n=a)}n===-1&&(n=s.lines.length-1),s.lines.splice(n+1,0,i)}}function ca(s,e){if(s.lines.some(i=>i.startsWith("a=extmap:")&&i.includes(e)))return;let t=0,r=-1;for(let i=0;i<s.lines.length;i++){let n=s.lines[i].match(/^a=extmap:(\d+)/);n&&(t=Math.max(t,Number(n[1])),r=i)}r===-1&&(r=s.lines.findIndex(i=>i.startsWith("a=mid:")),r===-1&&(r=s.lines.length-1)),s.lines.splice(r+1,0,`a=extmap:${t+1} ${e}`)}function Qi(s,e){if(!s||typeof s!="string"||!e)return s;let t=Wi(s),r=!1;for(let i of t.media){let n=Xi(i),a=n==="video"?e.video:n==="audio"?e.audio:null;a&&(Array.isArray(a.rtcpFb)&&(oa(i,a.rtcpFb),r=!0),a.twcc&&e.twccUri&&(ca(i,e.twccUri),r=!0))}return r?Yi(t):s}function Ji(s){try{if(Ue||!s||typeof s.setCodecPreferences!="function"||!st.preferRed)return!1;let e=typeof RTCRtpSender<"u"&&RTCRtpSender.getCapabilities?RTCRtpSender.getCapabilities("audio"):null;if(!e||!Array.isArray(e.codecs))return!1;let t=o=>/red$/i.test(o.mimeType),r=o=>/opus$/i.test(o.mimeType);if(!e.codecs.some(t))return!1;let i=e.codecs.filter(t),n=e.codecs.filter(r),a=e.codecs.filter(o=>!t(o)&&!r(o));return s.setCodecPreferences([...i,...n,...a]),!0}catch{return!1}}async function Zi(s,e={}){try{if(Ue||!s||typeof s.getParameters!="function")return!1;let t={...st.sender,...e},r=s.getParameters();(!r.encodings||r.encodings.length===0)&&(r.encodings=[{}]);for(let i of r.encodings)i.maxBitrate=t.maxBitrate,i.priority=t.priority,i.networkPriority=t.networkPriority;return await s.setParameters(r),!0}catch{return!1}}var en={VP9:0,AV1:1,H264:2,VP8:3};function tn(s){return(String(s||"").split("/")[1]||"").toUpperCase()}function la(s){let e=t=>{let r=tn(t.mimeType);return Object.prototype.hasOwnProperty.call(en,r)?en[r]:99};return s.map((t,r)=>({c:t,i:r})).sort((t,r)=>e(t.c)-e(r.c)||t.i-r.i).map(t=>t.c)}function rn(s){if(!s||!Array.isArray(s.codecs))return null;let e=new Set(s.codecs.map(t=>tn(t.mimeType)));for(let t of Re.codecPreferenceOrder)if(e.has(t))return t;return null}function nn(){return typeof RTCRtpSender<"u"&&RTCRtpSender.getCapabilities?RTCRtpSender.getCapabilities("video"):null}function sn(s){try{if(Ue||!s||typeof s.setCodecPreferences!="function")return!1;let e=nn();return!e||!Array.isArray(e.codecs)?!1:(s.setCodecPreferences(la(e.codecs)),rn(e))}catch{return!1}}function da(s){return s==="VP9"?{scalabilityMode:Re.vp9.preferredScalabilityMode,maxBitrate:15e5,degradationPreference:Re.vp9.degradationPreference}:s==="AV1"?{scalabilityMode:Re.av1.scalabilityMode,maxBitrate:Re.av1.maxBitrate,degradationPreference:Re.av1.degradationPreference}:{scalabilityMode:void 0,maxBitrate:15e5,degradationPreference:"balanced"}}async function an(s,e={}){try{if(Ue||!s||typeof s.getParameters!="function")return!1;let t=rn(nn())||"VP8",r={...da(t),...e},i=s.getParameters();(!i.encodings||i.encodings.length===0)&&(i.encodings=[{}]);let n=i.encodings.length>1;if(n)for(let a of i.encodings)a.networkPriority=Re.networkPriority;else{let a=i.encodings[0];a.maxBitrate=r.maxBitrate,a.networkPriority=Re.networkPriority,r.scalabilityMode&&(a.scalabilityMode=r.scalabilityMode)}r.degradationPreference&&(i.degradationPreference=r.degradationPreference);try{retu Line truncated
`)+`\r `)+`\r
`}}var Fr=new TextEncoder;function zt(...n){let e=n.reduce((i,s)=>i+s.length,0),t=new Uint8Array(e),r=0;for(let i of n)t.set(i,r),r+=i.length;return t}async function Kr(n,e){return(await n(zt(Fr.encode("sbq2/bind\0"),e))).slice(0,$.BINDING_BYTES)}async function Vt(n,e){return(await n(zt(Fr.encode("sbq2/blob\0"),e))).slice(0,$.COMMITMENT_BYTES)}function ys(n,e,t,r){let i=s=>{let a=new Uint8Array(4);return new DataView(a.buffer).setUint32(0,s.length),zt(a,s)};return zt(Fr.encode("sbq2/sas/v1\0"),i(n),i(e),i(t),i(r))}var mt="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";function va(n){let e="";for(let t=0;t<n.length;t+=3){let r=n[t],i=n[t+1],s=n[t+2];if(e+=mt[r>>2],e+=mt[(r&3)<<4|(i??0)>>4],i===void 0||(e+=mt[(i&15)<<2|(s??0)>>6],s===void 0))break;e+=mt[s&63]}return e}function Ta(n){typeof n!="string"&&P("payload must be a string");let e=n.replace(/\s+/g,"");e.length>Math.ceil($.MAX_PAYLOAD_BYTES*4/3)+4&&P("payload is too long"),/^[A-Za-z0-9_-]*$/.test(e)||P("payload contains characters outside base64url"),e.length%4===1&&P("payload has an impossible length");let t=new Uint8Array(Math.floor(e.length*3/4)),r=0,i=0,s=0;for(let a of e)i=i<<6|mt.indexOf(a),s+=6,s>=8&&(s-=8,t[r++]=i>>s&255);return i&(1<<s)-1&&P("payload has non-zero padding bits"),t.subarray(0,r)}var Ir="SB2:";function gs(n){return Ir+va(n)}function Nr(n){typeof n!="string"&&P("payload must be a string");let e=n.trim();return e.startsWith(Ir)||P("not an SB2 descriptor"),Ta(e.slice(Ir.length))}var ms=2,Ee=Object.freeze({OFFER:0,ANSWER:1}),We=Object.freeze({MAX_SPKI:256,MIN_SPKI:40,MAX_SIG:160,MIN_SIG:48,MAX_BLOB_BYTES:1024}),Or=class extends Error{constructor(e,t="key_exchange"){super(e),this.name="KeyExchangeError",this.code=t}},le=(n,e)=>{throw new Or(n,e)};function Ss({role:n,ecdhSpki:e,ecdsaSpki:t}){n!==Ee.OFFER&&n!==Ee.ANSWER&&le("invalid role");for(let[a,o]of[["ecdh",e],["ecdsa",t]])o instanceof Uint8Array||le(`${a} SPKI must be a Uint8Array`),(o.length<We.MIN_SPKI||o.length>We.MAX_SPKI)&&le(`${a} SPKI length out of range`);let r=new Uint8Array(4+e.length+2+t.length),i=new DataView(r.buffer),s=0;return r[s++]=ms,r[s++]=n,i.setUint16(s,e.length),s+=2,r.set(e,s),s+=e.length,i.setUint16(s,t.length),s+=2,r.set(t,s),r}function _s(n){n instanceof Uint8Array||le("key blob must be a Uint8Array"),n.length===0&&le("key blob is empty"),n.length>We.MAX_BLOB_BYTES&&le("key blob exceeds the size limit");let e=new DataView(n.buffer,n.byteOffset,n.byteLength),t=0,r=u=>{t+u>n.length&&le("key blob is truncated")};r(1);let i=n[t++];i!==ms&&le(`unsupported key blob version 0x${i.toString(16)}`,"version"),r(1);let s=n[t++];s!==Ee.OFFER&&s!==Ee.ANSWER&&le("reserved key blob role"),r(2);let a=e.getUint16(t);t+=2,(a<We.MIN_SPKI||a>We.MAX_SPKI)&&le("ECDH SPKI length out of range"),r(a);let o=n.slice(t,t+a);t+=a,r(2);let c=e.getUint16(t);t+=2,(c<We.MIN_SPKI||c>We.MAX_SPKI)&&le("ECDSA SPKI length out of range"),r(c);let d=n.slice(t,t+c);return t+=c,t!==n.length&&le(`${n.length-t} trailing byte(s) after the key blob`),{version:i,role:s,ecdhSpki:o,ecdsaSpki:d}}function bs({offerDescriptor:n,answerDescriptor:e,offerBlob:t,answerBlob:r}){for(let[i,s]of Object.entries({offerDescriptor:n,answerDescriptor:e,offerBlob:t,answerBlob:r}))(!(s instanceof Uint8Array)||s.length===0)&&le(`transcript component ${i} is missing`);return ys(n,e,t,r)}async function ws(n,e){let t=await n.digest("SHA-512",e);return Array.from(new Uint8Array(t))}var Es=new TextEncoder;function Ur(n){let e=Es.encode("sbq2/proof/v1\0"),t=new Uint8Array(e.length+n.length);return t.set(e,0),t.set(n,e.length),t}async function vs(n,{ecdhPrivateKey:e,peerEcdhPublicKey:t,transcript:r,digits:i=7}){let s=await n.deriveBits({name:"ECDH",public:t},e,256),a=null;try{a=await n.importKey("raw",s,"HKDF",!1,["deriveBits"]);let o=new Uint8Array(await n.digest("SHA-256",r)),c=await n.deriveBits({name:"HKDF",hash:"SHA-256",salt:o,info:Es.encode("sbq2-sas-v1")},a,64),d=new DataView(c),u=(d.getUint32(0)^d.getUint32(4))>>>0,h=10**i;return String(u%h).padStart(i,"0")}finally{try{new Uint8Array(s).fill(0)}catch{}}}async function Ts(n,e,t){(!(t instanceof Uint8Array)||t.length!==$.COMMITMENT_BYTES)&&le("descriptor carried no usable commitment","commitment_missing");let i=await Vt(async a=>new Uint8Array(await n.digest("SHA-256",a)),e);i.length!==t.length&&le("commitment length mismatch","commitment_mismatch");let s=0;for(let a=0;a<i.length;a++)s|=i[a]^t[a];return s!==0&&le("the key material does not match the commitment in the invitation","commitment_mismatch"),!0}var Bt=class n{static TIMEOUTS={KEY_ROTATION_INTERVAL:3e5,CONNECTION_TIMEOUT:1e4,HEARTBEAT_INTERVAL:1e4,SECURITY_CALC_DELAY:1e3,SECURITY_CALC_RETRY_DELAY:3e3,CLEANUP_INTERVAL:3e5,CLEANUP_CHECK_INTERVAL:6e4,ICE_GATHERING_TIMEOUT:1e4,ICE_GATHERING_HARD_TIMEOUT:25e3,DISCONNECT_CLEANUP_DELAY:500,PEER_DISCONNECT_CLEANUP:2e3,STAGE2_ACTIVATION_DELAY:1e4,STAGE3_ACTIVATION_DELAY:15e3,STAGE4_ACTIVATION_DELAY:2e4,FILE_TRANSFER_INIT_DELAY:1e3,FAKE_TRAFFIC_MIN_INTERV Line truncated `}}var Nr=new TextEncoder;function Bt(...s){let e=s.reduce((i,n)=>i+n.length,0),t=new Uint8Array(e),r=0;for(let i of s)t.set(i,r),r+=i.length;return t}async function Or(s,e){return(await s(Bt(Nr.encode("sbq2/bind\0"),e))).slice(0,j.BINDING_BYTES)}async function Ht(s,e){return(await s(Bt(Nr.encode("sbq2/blob\0"),e))).slice(0,j.COMMITMENT_BYTES)}function En(s,e,t,r){let i=n=>{let a=new Uint8Array(4);return new DataView(a.buffer).setUint32(0,n.length),Bt(a,n)};return Bt(Nr.encode("sbq2/sas/v1\0"),i(s),i(e),i(t),i(r))}var mt="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";function xa(s){let e="";for(let t=0;t<s.length;t+=3){let r=s[t],i=s[t+1],n=s[t+2];if(e+=mt[r>>2],e+=mt[(r&3)<<4|(i??0)>>4],i===void 0||(e+=mt[(i&15)<<2|(n??0)>>6],n===void 0))break;e+=mt[n&63]}return e}function Ia(s){typeof s!="string"&&P("payload must be a string");let e=s.replace(/\s+/g,"");e.length>Math.ceil(j.MAX_PAYLOAD_BYTES*4/3)+4&&P("payload is too long"),/^[A-Za-z0-9_-]*$/.test(e)||P("payload contains characters outside base64url"),e.length%4===1&&P("payload has an impossible length");let t=new Uint8Array(Math.floor(e.length*3/4)),r=0,i=0,n=0;for(let a of e)i=i<<6|mt.indexOf(a),n+=6,n>=8&&(n-=8,t[r++]=i>>n&255);return i&(1<<n)-1&&P("payload has non-zero padding bits"),t.subarray(0,r)}var Mr="SB2:";function vn(s){return Mr+xa(s)}function Ur(s){typeof s!="string"&&P("payload must be a string");let e=s.trim();return e.startsWith(Mr)||P("not an SB2 descriptor"),Ia(e.slice(Mr.length))}var Tn=2,ve=Object.freeze({OFFER:0,ANSWER:1}),We=Object.freeze({MAX_SPKI:256,MIN_SPKI:40,MAX_SIG:160,MIN_SIG:48,MAX_BLOB_BYTES:1024}),zr=class extends Error{constructor(e,t="key_exchange"){super(e),this.name="KeyExchangeError",this.code=t}},he=(s,e)=>{throw new zr(s,e)};function Cn({role:s,ecdhSpki:e,ecdsaSpki:t}){s!==ve.OFFER&&s!==ve.ANSWER&&he("invalid role");for(let[a,o]of[["ecdh",e],["ecdsa",t]])o instanceof Uint8Array||he(`${a} SPKI must be a Uint8Array`),(o.length<We.MIN_SPKI||o.length>We.MAX_SPKI)&&he(`${a} SPKI length out of range`);let r=new Uint8Array(4+e.length+2+t.length),i=new DataView(r.buffer),n=0;return r[n++]=Tn,r[n++]=s,i.setUint16(n,e.length),n+=2,r.set(e,n),n+=e.length,i.setUint16(n,t.length),n+=2,r.set(t,n),r}function kn(s){s instanceof Uint8Array||he("key blob must be a Uint8Array"),s.length===0&&he("key blob is empty"),s.length>We.MAX_BLOB_BYTES&&he("key blob exceeds the size limit");let e=new DataView(s.buffer,s.byteOffset,s.byteLength),t=0,r=u=>{t+u>s.length&&he("key blob is truncated")};r(1);let i=s[t++];i!==Tn&&he(`unsupported key blob version 0x${i.toString(16)}`,"version"),r(1);let n=s[t++];n!==ve.OFFER&&n!==ve.ANSWER&&he("reserved key blob role"),r(2);let a=e.getUint16(t);t+=2,(a<We.MIN_SPKI||a>We.MAX_SPKI)&&he("ECDH SPKI length out of range"),r(a);let o=s.slice(t,t+a);t+=a,r(2);let c=e.getUint16(t);t+=2,(c<We.MIN_SPKI||c>We.MAX_SPKI)&&he("ECDSA SPKI length out of range"),r(c);let d=s.slice(t,t+c);return t+=c,t!==s.length&&he(`${s.length-t} trailing byte(s) after the key blob`),{version:i,role:n,ecdhSpki:o,ecdsaSpki:d}}function An({offerDescriptor:s,answerDescriptor:e,offerBlob:t,answerBlob:r}){for(let[i,n]of Object.entries({offerDescriptor:s,answerDescriptor:e,offerBlob:t,answerBlob:r}))(!(n instanceof Uint8Array)||n.length===0)&&he(`transcript component ${i} is missing`);return En(s,e,t,r)}async function xn(s,e){let t=await s.digest("SHA-512",e);return Array.from(new Uint8Array(t))}var In=new TextEncoder;function Vr(s){let e=In.encode("sbq2/proof/v1\0"),t=new Uint8Array(e.length+s.length);return t.set(e,0),t.set(s,e.length),t}async function Rn(s,{ecdhPrivateKey:e,peerEcdhPublicKey:t,transcript:r,digits:i=7}){let n=await s.deriveBits({name:"ECDH",public:t},e,256),a=null;try{a=await s.importKey("raw",n,"HKDF",!1,["deriveBits"]);let o=new Uint8Array(await s.digest("SHA-256",r)),c=await s.deriveBits({name:"HKDF",hash:"SHA-256",salt:o,info:In.encode("sbq2-sas-v1")},a,64),d=new DataView(c),u=(d.getUint32(0)^d.getUint32(4))>>>0,h=10**i;return String(u%h).padStart(i,"0")}finally{try{new Uint8Array(n).fill(0)}catch{}}}async function Mn(s,e,t){(!(t instanceof Uint8Array)||t.length!==j.COMMITMENT_BYTES)&&he("descriptor carried no usable commitment","commitment_missing");let i=await Ht(async a=>new Uint8Array(await s.digest("SHA-256",a)),e);i.length!==t.length&&he("commitment length mismatch","commitment_mismatch");let n=0;for(let a=0;a<i.length;a++)n|=i[a]^t[a];return n!==0&&he("the key material does not match the commitment in the invitation","commitment_mismatch"),!0}var $t=class s{static TIMEOUTS={KEY_ROTATION_INTERVAL:3e5,CONNECTION_TIMEOUT:1e4,HEARTBEAT_INTERVAL:1e4,SECURITY_CALC_DELAY:1e3,SECURITY_CALC_RETRY_DELAY:3e3,CLEANUP_INTERVAL:3e5,CLEANUP_CHECK_INTERVAL:6e4,ICE_GATHERING_TIMEOUT:1e4,ICE_GATHERING_HARD_TIMEOUT:25e3,DISCONNECT_CLEANUP_DELAY:500,PEER_DISCONNECT_CLEANUP:2e3,STAGE2_ACTIVATION_DELAY:1e4,STAGE3_ACTIVATION_DELAY:15e3,STAGE4_ACTIVATION_DELAY:2e4,FILE_TRANSFER_INIT_DELAY:1e3,FAKE_TRAFFIC_MIN_INTERV Line truncated
self.onmessage = function(e) { self.onmessage = function(e) {
const { type, data } = e.data; const { type, data } = e.data;
@@ -39,10 +39,10 @@ Please wait for real-time cryptographic verification to complete.`,"sec.verifica
self.postMessage({ success: false, error: error.message }); self.postMessage({ success: false, error: error.message });
} }
}; };
`,s=new Blob([i],{type:"application/javascript"}),a=new Worker(URL.createObjectURL(s)),o=setTimeout(()=>{a.terminate(),r(new Error("Worker cleanup timeout"))},5e3);a.onmessage=c=>{clearTimeout(o),a.terminate(),URL.revokeObjectURL(s),c.data.success?t(c.data):r(new Error(c.data.error))},a.onerror=c=>{clearTimeout(o),a.terminate(),URL.revokeObjectURL(s),r(c)},a.postMessage(e)})}async _cleanupInMainThread(e){let{type:t,data:r}=e;switch(t){case"cleanup_arrays":let i=0,s=100;for(;i<r.count;){let d=Math.min(i+s,r.count);for(let u=i;u<d;u++);i=d,await this._asyncSleep(1)}return{success:!0,processed:i};case"cleanup_objects":let a=r.objects||[],o=[];for(let d=0;d<a.length;d+=50)o.push(a.slice(d,d+50));let c=0;for(let d of o)d.forEach(()=>c++),await this._asyncSleep(1);return{success:!0,cleaned:c};default:return{success:!0,message:"Unknown cleanup type"}}}_initializeMutexSystem(){this._keyOperationMutex={locked:!1,queue:[],lockId:null,lockTimeout:null,lockTime:null,operationCount:0},this._cryptoOperationMutex={locked:!1,queue:[],lockId:null,lockTimeout:null,lockTime:null,operationCount:0},this._connectionOperationMutex={locked:!1,queue:[],lockId:null,lockTimeout:null,lockTime:null,operationCount:0},this._keySystemState={isInitializing:!1,isRotating:!1,isDestroying:!1,lastOperation:null,lastOperationTime:Date.now(),operationId:null,concurrentOperations:0,maxConcurrentOperations:1},this._operationCounters={keyOperations:0,cryptoOperations:0,connectionOperations:0,totalOperations:0,failedOperations:0},this._secureLog("info","\u{1F512} Enhanced mutex system initialized with atomic protection",{mutexes:["keyOperation","cryptoOperation","connectionOperation"],timestamp:Date.now(),features:["atomic_operations","race_condition_protection","enhanced_state_tracking"]})}_hardenDebugModeReferences(){this._secureLog("info","\u{1F512} XSS Hardening: Debug mode references already replaced")}_initializeUnifiedScheduler(){this._maintenanceScheduler=setInterval(()=>{this._executeMaintenanceCycle()},3e5),this._secureLog("info","\u{1F527} Unified maintenance scheduler initialized (5-minute cycle)"),this._activeTimers=new Set([this._maintenanceScheduler])}_trackActiveTimer(e){return e&&(this._activeTimers||(this._activeTimers=new Set),this._activeTimers.add(e),e)}_untrackActiveTimer(e){e&&this._activeTimers&&this._activeTimers.delete(e)}_setSASMaterialReady(e,t){this._sasLocalFingerprint=e,this._sasRemoteFingerprint=t}_isVerificationReady(){let e=!!(this.peerConnection?.localDescription&&this.peerConnection?.remoteDescription),t=this.dataChannel?.readyState==="open",r=typeof this.verificationCode=="string"&&this.verificationCode.trim().length>0,i=typeof this._sasLocalFingerprint=="string"&&this._sasLocalFingerprint.trim().length>0&&typeof this._sasRemoteFingerprint=="string"&&this._sasRemoteFingerprint.trim().length>0;return e&&t&&r&&i}_notifyVerificationReadyIfPossible(){return this._isVerificationReady()?(this._verificationUiOpened||(this._verificationUiOpened=!0,this.onStatusChange?.("verifying"),this.onVerificationRequired?.(this.verificationCode)),!0):!1}_countIceCandidatesInSDP(e){return typeof e!="string"?0:(e.match(/^a=candidate:/gm)||[]).length}_summarizeIceCandidatesInSDP(e){let t={total:0,host:0,srflx:0,relay:0,prflx:0,unknown:0};if(typeof e!="string")return t;for(let r of e.match(/^a=candidate:.*$/gm)||[]){t.total+=1;let s=r.match(/\btyp\s+(host|srflx|relay|prflx)\b/i)?.[1]?.toLowerCase();s&&Object.prototype.hasOwnProperty.call(t,s)?t[s]+=1:t.unknown+=1}return t}_describeIceCandidatesInSDP(e){return typeof e!="string"?[]:(e.match(/^a=candidate:.*$/gm)||[]).map(t=>{let r=t.slice(12).trim().split(/\s+/),i=r.findIndex(u=>u.toLowerCase()==="typ"),s=r[4]||"",a=r[5]||"",o=i>=0&&r[i+1]||"unknown",c=(r[2]||"unknown").toLowerCase(),d="unknown";return/\.local$/i.test(s)?d="mdns":/^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[0-1])\.)/.test(s)?d="private-ipv4":/^\d{1,3}(\.\d{1,3}){3}$/.test(s)?d="public-ipv4":s.includes(":")&&(d="ipv6"),{candidateType:o,protocol:c,addressKind:d,portPresent:!!a,tcpType:(()=>{let u=r.findIndex(h=>h.toLowerCase()==="tcptype");return u>=0&&r[u+1]||null})()}})}_logIceCandidateDiagnostics(e,t,r={}){let i=this._summarizeIceCandidatesInSDP(t),s=this._describeIceCandidatesInSDP(t);return console.info(`[SecureBit ICE] ${e}`,{candidateSummary:i,candidateDetails:s,candidateDetailsJson:JSON.stringify(s),...r}),{candidateSummary:i,candidateDetails:s}}_hasOnlyMdnsHostCandidates(e){let t=this._summarizeIceCandidatesInSDP(e),r=this._describeIceCandidatesInSDP(e);return t.total>0&&t.srflx===0&&t.relay===0&&t.prflx===0&&r.every(i=>i.candidateType==="host"&&i.addressKind==="mdns")}_warnIfRemoteCandidatesNeedRelay(e,t){if(!this._hasOnlyMdnsHostCandidates(t))return!1;let r=e==="answer"?"Connection warning: the response contains only browser-masked mDNS host candidates and no server-reflexive or TURN relay candidates. This network/browser combination may not connect until TURN is configured.":"Connection warning: the i Line truncated `,n=new Blob([i],{type:"application/javascript"}),a=new Worker(URL.createObjectURL(n)),o=setTimeout(()=>{a.terminate(),r(new Error("Worker cleanup timeout"))},5e3);a.onmessage=c=>{clearTimeout(o),a.terminate(),URL.revokeObjectURL(n),c.data.success?t(c.data):r(new Error(c.data.error))},a.onerror=c=>{clearTimeout(o),a.terminate(),URL.revokeObjectURL(n),r(c)},a.postMessage(e)})}async _cleanupInMainThread(e){let{type:t,data:r}=e;switch(t){case"cleanup_arrays":let i=0,n=100;for(;i<r.count;){let d=Math.min(i+n,r.count);for(let u=i;u<d;u++);i=d,await this._asyncSleep(1)}return{success:!0,processed:i};case"cleanup_objects":let a=r.objects||[],o=[];for(let d=0;d<a.length;d+=50)o.push(a.slice(d,d+50));let c=0;for(let d of o)d.forEach(()=>c++),await this._asyncSleep(1);return{success:!0,cleaned:c};default:return{success:!0,message:"Unknown cleanup type"}}}_initializeMutexSystem(){this._keyOperationMutex={locked:!1,queue:[],lockId:null,lockTimeout:null,lockTime:null,operationCount:0},this._cryptoOperationMutex={locked:!1,queue:[],lockId:null,lockTimeout:null,lockTime:null,operationCount:0},this._connectionOperationMutex={locked:!1,queue:[],lockId:null,lockTimeout:null,lockTime:null,operationCount:0},this._keySystemState={isInitializing:!1,isRotating:!1,isDestroying:!1,lastOperation:null,lastOperationTime:Date.now(),operationId:null,concurrentOperations:0,maxConcurrentOperations:1},this._operationCounters={keyOperations:0,cryptoOperations:0,connectionOperations:0,totalOperations:0,failedOperations:0},this._secureLog("info","\u{1F512} Enhanced mutex system initialized with atomic protection",{mutexes:["keyOperation","cryptoOperation","connectionOperation"],timestamp:Date.now(),features:["atomic_operations","race_condition_protection","enhanced_state_tracking"]})}_hardenDebugModeReferences(){this._secureLog("info","\u{1F512} XSS Hardening: Debug mode references already replaced")}_initializeUnifiedScheduler(){this._maintenanceScheduler=setInterval(()=>{this._executeMaintenanceCycle()},3e5),this._secureLog("info","\u{1F527} Unified maintenance scheduler initialized (5-minute cycle)"),this._activeTimers=new Set([this._maintenanceScheduler])}_trackActiveTimer(e){return e&&(this._activeTimers||(this._activeTimers=new Set),this._activeTimers.add(e),e)}_untrackActiveTimer(e){e&&this._activeTimers&&this._activeTimers.delete(e)}_setSASMaterialReady(e,t){this._sasLocalFingerprint=e,this._sasRemoteFingerprint=t}_isVerificationReady(){let e=!!(this.peerConnection?.localDescription&&this.peerConnection?.remoteDescription),t=this.dataChannel?.readyState==="open",r=typeof this.verificationCode=="string"&&this.verificationCode.trim().length>0,i=typeof this._sasLocalFingerprint=="string"&&this._sasLocalFingerprint.trim().length>0&&typeof this._sasRemoteFingerprint=="string"&&this._sasRemoteFingerprint.trim().length>0;return e&&t&&r&&i}_notifyVerificationReadyIfPossible(){return this._isVerificationReady()?(this._verificationUiOpened||(this._verificationUiOpened=!0,this.onStatusChange?.("verifying"),this.onVerificationRequired?.(this.verificationCode)),!0):!1}_countIceCandidatesInSDP(e){return typeof e!="string"?0:(e.match(/^a=candidate:/gm)||[]).length}_summarizeIceCandidatesInSDP(e){let t={total:0,host:0,srflx:0,relay:0,prflx:0,unknown:0};if(typeof e!="string")return t;for(let r of e.match(/^a=candidate:.*$/gm)||[]){t.total+=1;let n=r.match(/\btyp\s+(host|srflx|relay|prflx)\b/i)?.[1]?.toLowerCase();n&&Object.prototype.hasOwnProperty.call(t,n)?t[n]+=1:t.unknown+=1}return t}_describeIceCandidatesInSDP(e){return typeof e!="string"?[]:(e.match(/^a=candidate:.*$/gm)||[]).map(t=>{let r=t.slice(12).trim().split(/\s+/),i=r.findIndex(u=>u.toLowerCase()==="typ"),n=r[4]||"",a=r[5]||"",o=i>=0&&r[i+1]||"unknown",c=(r[2]||"unknown").toLowerCase(),d="unknown";return/\.local$/i.test(n)?d="mdns":/^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[0-1])\.)/.test(n)?d="private-ipv4":/^\d{1,3}(\.\d{1,3}){3}$/.test(n)?d="public-ipv4":n.includes(":")&&(d="ipv6"),{candidateType:o,protocol:c,addressKind:d,portPresent:!!a,tcpType:(()=>{let u=r.findIndex(h=>h.toLowerCase()==="tcptype");return u>=0&&r[u+1]||null})()}})}_logIceCandidateDiagnostics(e,t,r={}){let i=this._summarizeIceCandidatesInSDP(t),n=this._describeIceCandidatesInSDP(t);return console.info(`[SecureBit ICE] ${e}`,{candidateSummary:i,candidateDetails:n,candidateDetailsJson:JSON.stringify(n),...r}),{candidateSummary:i,candidateDetails:n}}_hasOnlyMdnsHostCandidates(e){let t=this._summarizeIceCandidatesInSDP(e),r=this._describeIceCandidatesInSDP(e);return t.total>0&&t.srflx===0&&t.relay===0&&t.prflx===0&&r.every(i=>i.candidateType==="host"&&i.addressKind==="mdns")}_warnIfRemoteCandidatesNeedRelay(e,t){if(!this._hasOnlyMdnsHostCandidates(t))return!1;let r=e==="answer"?"Connection warning: the response contains only browser-masked mDNS host candidates and no server-reflexive or TURN relay candidates. This network/browser combination may not connect until TURN is configured.":"Connection warning: the i Line truncated
`),e=e.replace(/\n{3,}/g,` `),e=e.replace(/\n{3,}/g,`
`),e=e.trim()),e}_sanitizeInputObject(e){if(e===null||typeof e!="object")return e;if(Array.isArray(e))return e.map(r=>this._sanitizeInputObject(r));let t={};for(let r in e)if(e.hasOwnProperty(r)){let i=e[r];typeof i=="string"?t[r]=this._sanitizeInputString(i):typeof i=="object"?t[r]=this._sanitizeInputObject(i):t[r]=i}return t}_checkRateLimit(e="message"){let t=Date.now();return this._rateLimiter||(this._rateLimiter={messageCount:0,lastReset:t,burstCount:0,lastBurstReset:t}),t-this._rateLimiter.lastReset>6e4&&(this._rateLimiter.messageCount=0,this._rateLimiter.lastReset=t),t-this._rateLimiter.lastBurstReset>1e3&&(this._rateLimiter.burstCount=0,this._rateLimiter.lastBurstReset=t),this._rateLimiter.burstCount>=this._inputValidationLimits.rateLimitBurstSize?(this._secureLog("warn","\u26A0\uFE0F Rate limit burst exceeded",{context:e}),!1):this._rateLimiter.messageCount>=this._inputValidationLimits.rateLimitMessagesPerMinute?(this._secureLog("warn","\u26A0\uFE0F Rate limit exceeded",{context:e}),!1):(this._rateLimiter.messageCount++,this._rateLimiter.burstCount++,!0)}_checkInboundRateLimit(e="incoming_message"){let t=Date.now();return this._inboundRateLimiter||(this._inboundRateLimiter={messageCount:0,lastReset:t,burstCount:0,lastBurstReset:t}),t-this._inboundRateLimiter.lastReset>6e4&&(this._inboundRateLimiter.messageCount=0,this._inboundRateLimiter.lastReset=t),t-this._inboundRateLimiter.lastBurstReset>1e3&&(this._inboundRateLimiter.burstCount=0,this._inboundRateLimiter.lastBurstReset=t),this._inboundRateLimiter.burstCount>=this._inputValidationLimits.rateLimitBurstSize?(this._secureLog("warn","\u26A0\uFE0F Inbound message burst limit exceeded; dropping message",{context:e}),!1):this._inboundRateLimiter.messageCount>=this._inputValidationLimits.rateLimitMessagesPerMinute?(this._secureLog("warn","\u26A0\uFE0F Inbound message rate limit exceeded; dropping message",{context:e}),!1):(this._inboundRateLimiter.messageCount++,this._inboundRateLimiter.burstCount++,!0)}_initializeSecureKeyStorage(){this._masterKeyManager=new Gt,this._secureKeyStorage=new Ht(this._masterKeyManager),this._keyStorageStats={totalKeys:0,activeKeys:0,lastAccess:null,lastRotation:null},this._secureLog("info","\u{1F510} Enhanced secure key storage initialized")}setMasterKeyPasswordCallback(e){this._masterKeyManager&&this._masterKeyManager.setPasswordRequiredCallback(e)}setMasterKeySessionExpiredCallback(e){this._masterKeyManager&&this._masterKeyManager.setSessionExpiredCallback(e)}lockMasterKey(){this._masterKeyManager&&this._masterKeyManager.lock()}isMasterKeyUnlocked(){return this._masterKeyManager?this._masterKeyManager.isUnlocked():!1}getMasterKeySessionStatus(){return this._masterKeyManager?this._masterKeyManager.getSessionStatus():null}async _ensureFileTransferReady(){try{if(this.fileTransferSystem)return!0;if(!this.dataChannel||this.dataChannel.readyState!=="open")throw new Error("Data channel not open");if(!this.isVerified)throw new Error("Connection not verified");this.initializeFileTransfer();let e=0,t=50;for(;!this.fileTransferSystem&&e<t;)await new Promise(r=>setTimeout(r,100)),e++;if(!this.fileTransferSystem)throw new Error("File transfer system initialization timeout");return!0}catch(e){return this._secureLog("error","\u274C _ensureFileTransferReady failed",{errorType:e?.constructor?.name||"Unknown",hasMessage:!!e?.message}),!1}}_getSecureKey(e){return this._secureKeyStorage.retrieveKey(e)}async _setSecureKey(e,t){if(!(t instanceof CryptoKey))return this._secureLog("error","\u274C Attempt to store non-CryptoKey"),!1;let r=await this._secureKeyStorage.storeKey(e,t,{version:this.currentKeyVersion,type:t.algorithm.name});return r&&this._secureLog("info",`\u{1F511} Key ${e} stored securely with encryption`),r}_validateKeyValue(e){return e instanceof CryptoKey&&e.algorithm&&e.usages&&e.usages.length>0}_secureWipeKeys(){this._secureKeyStorage.secureWipeAll(),this._masterKeyManager&&this._masterKeyManager.lock(),this._secureLog("info","\u{1F9F9} All keys securely wiped and encrypted storage cleared")}_validateKeyStorage(){return this._secureKeyStorage instanceof Ht}_getKeyStorageStats(){let e=this._secureKeyStorage.getStorageStats();return{totalKeysCount:e.totalKeys,activeKeysCount:e.totalKeys,hasLastAccess:e.metadata.some(t=>t.lastAccessed),hasLastRotation:!!this._keyStorageStats.lastRotation,storageType:"SecureKeyStorage",timestamp:Date.now()}}_rotateKeys(){let e=Array.from(this._secureKeyStorage.keys());this._secureKeyStorage.clear(),this._keyStorageStats.lastRotation=Date.now(),this._keyStorageStats.activeKeys=0,this._secureLog("info",`\u{1F504} Key rotation completed. ${e.length} keys rotated`)}_emergencyKeyWipe(){this._secureWipeKeys(),this._secureLog("error","\u{1F6A8} EMERGENCY: All keys wiped due to security threat")}_startKeySecurityMonitoring(){this._secureLog("info","\u{1F527} Key security monitoring moved to unified scheduler")}_validateKeyConstantTime(e){let t=0;try{let r=e instanceof CryptoKey;t+=r?1:0}catch{t+=0}try{let Line truncated `),e=e.trim()),e}_sanitizeInputObject(e){if(e===null||typeof e!="object")return e;if(Array.isArray(e))return e.map(r=>this._sanitizeInputObject(r));let t={};for(let r in e)if(e.hasOwnProperty(r)){let i=e[r];typeof i=="string"?t[r]=this._sanitizeInputString(i):typeof i=="object"?t[r]=this._sanitizeInputObject(i):t[r]=i}return t}_checkRateLimit(e="message"){let t=Date.now();return this._rateLimiter||(this._rateLimiter={messageCount:0,lastReset:t,burstCount:0,lastBurstReset:t}),t-this._rateLimiter.lastReset>6e4&&(this._rateLimiter.messageCount=0,this._rateLimiter.lastReset=t),t-this._rateLimiter.lastBurstReset>1e3&&(this._rateLimiter.burstCount=0,this._rateLimiter.lastBurstReset=t),this._rateLimiter.burstCount>=this._inputValidationLimits.rateLimitBurstSize?(this._secureLog("warn","\u26A0\uFE0F Rate limit burst exceeded",{context:e}),!1):this._rateLimiter.messageCount>=this._inputValidationLimits.rateLimitMessagesPerMinute?(this._secureLog("warn","\u26A0\uFE0F Rate limit exceeded",{context:e}),!1):(this._rateLimiter.messageCount++,this._rateLimiter.burstCount++,!0)}_checkInboundRateLimit(e="incoming_message"){let t=Date.now();return this._inboundRateLimiter||(this._inboundRateLimiter={messageCount:0,lastReset:t,burstCount:0,lastBurstReset:t}),t-this._inboundRateLimiter.lastReset>6e4&&(this._inboundRateLimiter.messageCount=0,this._inboundRateLimiter.lastReset=t),t-this._inboundRateLimiter.lastBurstReset>1e3&&(this._inboundRateLimiter.burstCount=0,this._inboundRateLimiter.lastBurstReset=t),this._inboundRateLimiter.burstCount>=this._inputValidationLimits.rateLimitBurstSize?(this._secureLog("warn","\u26A0\uFE0F Inbound message burst limit exceeded; dropping message",{context:e}),!1):this._inboundRateLimiter.messageCount>=this._inputValidationLimits.rateLimitMessagesPerMinute?(this._secureLog("warn","\u26A0\uFE0F Inbound message rate limit exceeded; dropping message",{context:e}),!1):(this._inboundRateLimiter.messageCount++,this._inboundRateLimiter.burstCount++,!0)}_initializeSecureKeyStorage(){this._masterKeyManager=new jt,this._secureKeyStorage=new Gt(this._masterKeyManager),this._keyStorageStats={totalKeys:0,activeKeys:0,lastAccess:null,lastRotation:null},this._secureLog("info","\u{1F510} Enhanced secure key storage initialized")}setMasterKeyPasswordCallback(e){this._masterKeyManager&&this._masterKeyManager.setPasswordRequiredCallback(e)}setMasterKeySessionExpiredCallback(e){this._masterKeyManager&&this._masterKeyManager.setSessionExpiredCallback(e)}lockMasterKey(){this._masterKeyManager&&this._masterKeyManager.lock()}isMasterKeyUnlocked(){return this._masterKeyManager?this._masterKeyManager.isUnlocked():!1}getMasterKeySessionStatus(){return this._masterKeyManager?this._masterKeyManager.getSessionStatus():null}async _ensureFileTransferReady(){try{if(this.fileTransferSystem)return!0;if(!this.dataChannel||this.dataChannel.readyState!=="open")throw new Error("Data channel not open");if(!this.isVerified)throw new Error("Connection not verified");this.initializeFileTransfer();let e=0,t=50;for(;!this.fileTransferSystem&&e<t;)await new Promise(r=>setTimeout(r,100)),e++;if(!this.fileTransferSystem)throw new Error("File transfer system initialization timeout");return!0}catch(e){return this._secureLog("error","\u274C _ensureFileTransferReady failed",{errorType:e?.constructor?.name||"Unknown",hasMessage:!!e?.message}),!1}}_getSecureKey(e){return this._secureKeyStorage.retrieveKey(e)}async _setSecureKey(e,t){if(!(t instanceof CryptoKey))return this._secureLog("error","\u274C Attempt to store non-CryptoKey"),!1;let r=await this._secureKeyStorage.storeKey(e,t,{version:this.currentKeyVersion,type:t.algorithm.name});return r&&this._secureLog("info",`\u{1F511} Key ${e} stored securely with encryption`),r}_validateKeyValue(e){return e instanceof CryptoKey&&e.algorithm&&e.usages&&e.usages.length>0}_secureWipeKeys(){this._secureKeyStorage.secureWipeAll(),this._masterKeyManager&&this._masterKeyManager.lock(),this._secureLog("info","\u{1F9F9} All keys securely wiped and encrypted storage cleared")}_validateKeyStorage(){return this._secureKeyStorage instanceof Gt}_getKeyStorageStats(){let e=this._secureKeyStorage.getStorageStats();return{totalKeysCount:e.totalKeys,activeKeysCount:e.totalKeys,hasLastAccess:e.metadata.some(t=>t.lastAccessed),hasLastRotation:!!this._keyStorageStats.lastRotation,storageType:"SecureKeyStorage",timestamp:Date.now()}}_rotateKeys(){let e=Array.from(this._secureKeyStorage.keys());this._secureKeyStorage.clear(),this._keyStorageStats.lastRotation=Date.now(),this._keyStorageStats.activeKeys=0,this._secureLog("info",`\u{1F504} Key rotation completed. ${e.length} keys rotated`)}_emergencyKeyWipe(){this._secureWipeKeys(),this._secureLog("error","\u{1F6A8} EMERGENCY: All keys wiped due to security threat")}_startKeySecurityMonitoring(){this._secureLog("info","\u{1F527} Key security monitoring moved to unified scheduler")}_validateKeyConstantTime(e){let t=0;try{let r=e instanceof CryptoKey;t+=r?1:0}catch{t+=0}try{let Line truncated
`;if(D+=`Security Level: ${I.level} (${I.score}%) `;if(D+=`Security Level: ${I.level} (${I.score}%)
`,D+=`Verification Time: ${new Date(I.timestamp).toLocaleTimeString()} `,D+=`Verification Time: ${new Date(I.timestamp).toLocaleTimeString()}
@@ -50,11 +50,11 @@ Please wait for real-time cryptographic verification to complete.`,"sec.verifica
`,I.verificationResults){D+=`DETAILED CRYPTOGRAPHIC TESTS: `,I.verificationResults){D+=`DETAILED CRYPTOGRAPHIC TESTS:
`,D+="="+"=".repeat(40)+` `,D+="="+"=".repeat(40)+`
`;let Y=Object.entries(I.verificationResults).filter(([_e,ye])=>ye.passed),Se=Object.entries(I.verificationResults).filter(([_e,ye])=>!ye.passed);Y.length>0&&(D+=`PASSED TESTS: `;let Z=Object.entries(I.verificationResults).filter(([_e,me])=>me.passed),be=Object.entries(I.verificationResults).filter(([_e,me])=>!me.passed);Z.length>0&&(D+=`PASSED TESTS:
`,Y.forEach(([_e,ye])=>{let Ye=_e.replace(/([A-Z])/g," $1").replace(/^./,W=>W.toUpperCase());D+=` ${Ye}: ${ye.details||f("sec.testPassed")} `,Z.forEach(([_e,me])=>{let Ye=_e.replace(/([A-Z])/g," $1").replace(/^./,X=>X.toUpperCase());D+=` ${Ye}: ${me.details||f("sec.testPassed")}
`}),D+=` `}),D+=`
`),Se.length>0&&(D+=`FAILED/UNAVAILABLE TESTS: `),be.length>0&&(D+=`FAILED/UNAVAILABLE TESTS:
`,Se.forEach(([_e,ye])=>{let Ye=_e.replace(/([A-Z])/g," $1").replace(/^./,W=>W.toUpperCase());D+=` ${Ye}: ${ye.details||f("sec.testFailed")} `,be.forEach(([_e,me])=>{let Ye=_e.replace(/([A-Z])/g," $1").replace(/^./,X=>X.toUpperCase());D+=` ${Ye}: ${me.details||f("sec.testFailed")}
`}),D+=` `}),D+=`
`),D+=`SUMMARY: `),D+=`SUMMARY:
`,D+=`Passed: ${I.passedChecks}/${I.totalChecks} tests `,D+=`Passed: ${I.passedChecks}/${I.totalChecks} tests
@@ -62,7 +62,7 @@ Please wait for real-time cryptographic verification to complete.`,"sec.verifica
`}if(D+=`SECURITY FEATURES STATUS: `}if(D+=`SECURITY FEATURES STATUS:
`,D+="="+"=".repeat(40)+` `,D+="="+"=".repeat(40)+`
`,I.verificationResults){let Y={"ECDSA Digital Signatures":I.verificationResults.verifyECDSASignatures?.passed||!1,"ECDH Key Exchange":I.verificationResults.verifyECDHKeyExchange?.passed||!1,"AES-GCM Encryption":I.verificationResults.verifyEncryption?.passed||!1,[f("sec.messageIntegrity")]:I.verificationResults.verifyMessageIntegrity?.passed||!1,[f("sec.forwardSecrecy")]:I.verificationResults.verifyPerfectForwardSecrecy?.passed||!1,[f("sec.replayProtection")]:I.verificationResults.verifyReplayProtection?.passed||!1,"DTLS Fingerprint":I.verificationResults.verifyDTLSFingerprint?.passed||!1,"SAS Verification":I.verificationResults.verifySASVerification?.passed||!1,[f("sec.metadataProtection")]:I.verificationResults.verifyMetadataProtection?.passed||!1,[f("sec.trafficObfuscation")]:I.verificationResults.verifyTrafficObfuscation?.passed||!1};Object.entries(Y).forEach(([Se,_e])=>{D+=`${_e?"\u2705":"\u274C"} ${Se} `,I.verificationResults){let Z={"ECDSA Digital Signatures":I.verificationResults.verifyECDSASignatures?.passed||!1,"ECDH Key Exchange":I.verificationResults.verifyECDHKeyExchange?.passed||!1,"AES-GCM Encryption":I.verificationResults.verifyEncryption?.passed||!1,[f("sec.messageIntegrity")]:I.verificationResults.verifyMessageIntegrity?.passed||!1,[f("sec.forwardSecrecy")]:I.verificationResults.verifyPerfectForwardSecrecy?.passed||!1,[f("sec.replayProtection")]:I.verificationResults.verifyReplayProtection?.passed||!1,"DTLS Fingerprint":I.verificationResults.verifyDTLSFingerprint?.passed||!1,"SAS Verification":I.verificationResults.verifySASVerification?.passed||!1,[f("sec.metadataProtection")]:I.verificationResults.verifyMetadataProtection?.passed||!1,[f("sec.trafficObfuscation")]:I.verificationResults.verifyTrafficObfuscation?.passed||!1};Object.entries(Z).forEach(([be,_e])=>{D+=`${_e?"\u2705":"\u274C"} ${be}
`})}else D+=`\u2705 ECDSA Digital Signatures `})}else D+=`\u2705 ECDSA Digital Signatures
`,D+=`\u2705 ECDH Key Exchange `,D+=`\u2705 ECDH Key Exchange
`,D+=`\u2705 AES-GCM Encryption `,D+=`\u2705 AES-GCM Encryption
@@ -78,7 +78,7 @@ ${I.details||f("sec.verificationDone")}`,I.isRealData?D+=`
\u2705 This is REAL-TIME verification using actual cryptographic functions.`:D+=` \u2705 This is REAL-TIME verification using actual cryptographic functions.`:D+=`
\u26A0\uFE0F Warning: This data may be simulated. Connection may not be fully established.`;let K=document.createElement("div");K.style.cssText=` \u26A0\uFE0F Warning: This data may be simulated. Connection may not be fully established.`;let N=document.createElement("div");N.style.cssText=`
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
@@ -90,7 +90,7 @@ ${I.details||f("sec.verificationDone")}`,I.isRealData?D+=`
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-family: monospace; font-family: monospace;
`;let O=document.createElement("div");O.style.cssText=` `;let z=document.createElement("div");z.style.cssText=`
background: var(--sb-legacy-panel); background: var(--sb-legacy-panel);
color: var(--sb-text-1); color: var(--sb-text-1);
padding: 20px; padding: 20px;
@@ -100,11 +100,11 @@ ${I.details||f("sec.verificationDone")}`,I.isRealData?D+=`
overflow-y: auto; overflow-y: auto;
white-space: pre-line; white-space: pre-line;
border: 1px solid var(--sb-surface-5); border: 1px solid var(--sb-surface-5);
`,O.textContent=D,K.appendChild(O),K.addEventListener("click",Y=>{Y.target===K&&document.body.removeChild(K)});let q=Y=>{Y.key==="Escape"&&(document.body.removeChild(K),document.removeEventListener("keydown",q))};document.addEventListener("keydown",q),document.body.appendChild(K)},F=(()=>{switch(n){case"connected":return{text:f("status.connected"),className:"status-connected",badgeClass:"bg-green-500/10 text-green-400 border-green-500/20"};case"verifying":return{text:f("status.verifying"),className:"status-verifying",badgeClass:"bg-purple-500/10 text-purple-400 border-purple-500/20"};case"connecting":return{text:f("status.connecting"),className:"status-connecting",badgeClass:"bg-blue-500/10 text-blue-400 border-blue-500/20"};case"retrying":return{text:f("status.retrying"),className:"status-connecting",badgeClass:"bg-yellow-500/10 text-yellow-400 border-yellow-500/20"};case"failed":return{text:f("status.error"),className:"status-failed",badgeClass:"bg-red-500/10 text-red-400 border-red-500/20"};case"reconnecting":return{text:f("status.reconnecting"),className:"status-connecting",badgeClass:"bg-yellow-500/10 text-yellow-400 border-yellow-500/20"};case"peer_disconnected":return{text:f("status.peerDisconnected"),className:"status-failed",badgeClass:"bg-orange-500/10 text-orange-400 border-orange-500/20"};default:return{text:f("status.notConnected"),className:"status-disconnected",badgeClass:"bg-gray-500/10 text-gray-400 border-gray-500/20"}}})(),k=i?o||s:null,b=(()=>{if(!k)return{tooltip:f("sec.verificationInProgress"),isVerified:!1,dataSource:"loading"};let G=k.isRealData!==!1,N=`${k.level} (${k.score}%)`;return G?{tooltip:`${N} - Real-time verification \u2705 `,z.textContent=D,N.appendChild(z),N.addEventListener("click",Z=>{Z.target===N&&document.body.removeChild(N)});let W=Z=>{Z.key==="Escape"&&(document.body.removeChild(N),document.removeEventListener("keydown",W))};document.addEventListener("keydown",W),document.body.appendChild(N)},F=(()=>{switch(s){case"connected":return{text:f("status.connected"),className:"status-connected",badgeClass:"bg-green-500/10 text-green-400 border-green-500/20"};case"verifying":return{text:f("status.verifying"),className:"status-verifying",badgeClass:"bg-purple-500/10 text-purple-400 border-purple-500/20"};case"connecting":return{text:f("status.connecting"),className:"status-connecting",badgeClass:"bg-blue-500/10 text-blue-400 border-blue-500/20"};case"retrying":return{text:f("status.retrying"),className:"status-connecting",badgeClass:"bg-yellow-500/10 text-yellow-400 border-yellow-500/20"};case"failed":return{text:f("status.error"),className:"status-failed",badgeClass:"bg-red-500/10 text-red-400 border-red-500/20"};case"reconnecting":return{text:f("status.reconnecting"),className:"status-connecting",badgeClass:"bg-yellow-500/10 text-yellow-400 border-yellow-500/20"};case"peer_disconnected":return{text:f("status.peerDisconnected"),className:"status-failed",badgeClass:"bg-orange-500/10 text-orange-400 border-orange-500/20"};default:return{text:f("status.notConnected"),className:"status-disconnected",badgeClass:"bg-gray-500/10 text-gray-400 border-gray-500/20"}}})(),k=i?o||n:null,_=(()=>{if(!k)return{tooltip:f("sec.verificationInProgress"),isVerified:!1,dataSource:"loading"};let G=k.isRealData!==!1,U=`${k.level} (${k.score}%)`;return G?{tooltip:`${U} - Real-time verification \u2705
Right-click or Ctrl+click to disconnect`,isVerified:!0,dataSource:"real"}:{tooltip:`${N} - Estimated (connection establishing...) Right-click or Ctrl+click to disconnect`,isVerified:!0,dataSource:"real"}:{tooltip:`${U} - Estimated (connection establishing...)
Right-click or Ctrl+click to disconnect`,isVerified:!1,dataSource:"estimated"}})();React.useEffect(()=>(window.debugHeaderSecurity=void 0,()=>{delete window.debugHeaderSecurity}),[o,d,i,a,k,b]);let A=k?k.color==="green"?"var(--sb-green)":k.color==="orange"?"var(--sb-orange)":k.color==="yellow"?"var(--sb-yellow)":"var(--sb-red)":"var(--sb-green)",x=i?"var(--sb-green-solid)":["connecting","verifying","retrying","reconnecting"].includes(n)?"var(--sb-yellow-solid)":n==="failed"?"var(--sb-red-solid)":"var(--sb-text-9)",T=x==="var(--sb-green-solid)"?"rgba(var(--sb-green-rgb), 0.16)":x==="var(--sb-yellow-solid)"?"rgba(var(--sb-yellow-rgb), 0.16)":x==="var(--sb-red-solid)"?"rgba(var(--sb-red-rgb), 0.16)":"rgba(var(--sb-text-9-rgb), 0.16)",j="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",z=!i,[R,fe]=React.useState(!1);React.useEffect(()=>{let G=()=>fe((window.scrollY||window.pageYOffset||0)>8);return G(),window.addEventListener("scroll",G,{passive:!0}),()=>window.removeEventListener("scroll",G)},[]);let re="blur(20px) saturate(180%)",pe="background .25s ease, backdrop-filter .25s ease, -webkit-backdrop-filter .25s ease, border-color .25s ease",me={paddingTop:"var(--sb-safe-top, 0px)",paddingBottom:"var(--sb-bar-extra, 0px)"},ce={position:"fixed",top:0,left:0,right:0,...me},ve=z?R?{...ce,background:"rgba(var(--sb-bg-rgb), 0.72)",backdropFilter:re,WebkitBackdropFilter:re,borderBottom:"1px solid rgba(var(--sb-ink), 0.06)",transition:pe}:{...ce,background:"transparent",backdropFilter:"blur(0px) saturate(100%)",WebkitBackdropFilter:"blur(0px) saturate(100%)",borderBottom:"1px solid transparent",transition:pe}:{...me,background:"rgba(var(--sb-bg-rgb), 0.72)",backdropFilter:re,WebkitBackdropFilter:re,borderBottom:"1px solid rgba(var(--sb-ink), 0.06)"};return React.createElement("header",{className:z?"header-minimal z-50":"header-minimal sticky top-0 z-50",style:ve},[React.createElement("div",{key:"container",className:"max-w-7xl mx-auto",style:{padding:"0 20px"}},[React.createElement("div",{key:"content",className:"flex items-center justify-between",style:{height:"var(--sb-bar-h, 64px)",gap:"16px"}},[React.createElement("div",{key:"left",style:{display:"flex",alignItems:"center",gap:"12px",minWidth:0}},[React.createElement("div",{key:"logo",style:{width:"36px",height:"36px",flex:"none",display:"grid",placeItems:"center"}},React.createElement("img",{src:"/logo/securebit-mark.svg",alt:"SecureBit",style:{width:"100%",height:"100%",objectFit:"contain",display:"block"}})),React.createElement("div",{key:"txt",style:{lineHeight:1.2,minWidth:0}},[React.createElement("div",{key:"r1",style:{display:"flex",alignItems:"baseline",gap:"7px"}},[React.createElement("span",{key:"n",style:{fontSize:"16px",fontWeight:800,letterSpacing:"-0.3px",color:"var(--sb-text-2)"}},"SecureBit"),React.createElement("span",{key:"v",style:{fontFamily:j,fontSize:"10px",fontWeight:500,color:"var(--sb-text-faint)"}},ka)]),React.createElement("div",{key:"r2",className:"hidden sm:block",style:{fontSize:"11px",color:"var(--sb-text-9)",fontWeight:500}},f("hdr.tagline"))])]),React.createElement("div",{key:"right",style:{display:"flex",alignItems:"center",gap:"9px"}},[z&&React.createElement(Vr,{key:"lang"}),z&&React.createElement(Hr,{key:"theme"}),!z&&React.createElement("button",{key:"net",type:"button",onClick:()=>window.dispatchEvent(new CustomEvent("securebit:open-network-settings")),title:f("hdr.netSettingsTitle"),"aria-label":f("hdr.netSettings"),className:"sb-disconnect",style:{display:"grid",placeItems:"center",width:"38px",height:"38px",borderRadius:"9px",border:"1px solid rgba(var(--sb-ink), 0.07)",background:"rgba(var(--sb-ink), 0.02)",color:"var(--sb-text-6)",cursor:"pointer",transition:"all .15s"}},React.createElement("i",{className:"fas fa-network-wired",style:{fontSize:"13px"}})),!z&&k&&React.createElement("div",{key:"sec",onClick:w,onContextMenu:G=>{G.preventDefault(),typeof r=="function"&&r()},title:b.tooltip,className:"sb-secpill",style:{display:"flex",alignItems:"center",gap:"8px",padding:"7px 12px",borderRadius:"9px",border:"1px solid rgba(var(--sb-ink), 0.07)",background:"rgba(var(--sb-ink), 0.02)",cursor:"pointer"}},[React.createElement("i",{key:"i",className:"fas fa-shield-halved",style:{fontSize:"13px",color:A}}),React.createElement("span",{key:"l",className:"hidden sm:inline",style:{fontSize:"12.5px",fontWeight:600,color:"var(--sb-text-2)"}},f(`secLevel.${k.level}`)===`secLevel.${k.level}`?String(k.level):f(`secLevel.${k.level}`)),React.createElement("span",{key:"s",style:{fontFamily:j,fontSize:"11.5px",color:"var(--sb-text-7)"}},k.score+"%")]),!z&&React.createElement("div",{key:"status",style:{display:"flex",alignItems:"center",gap:"8px",padding:"8px 13px",borderRadius:"9px",border:"1px solid rgba(var(--sb-ink), 0.07)",background:"rgba(var(--sb-ink), 0.02)"}},[React.createElement("span",{key:"dot",style:{width:"7px",height:"7px",borderRadius:"50%",background:x,boxShadow:"0 0 0 3px "+T}}),React.createElement("span",{key Line truncated Right-click or Ctrl+click to disconnect`,isVerified:!1,dataSource:"estimated"}})();React.useEffect(()=>(window.debugHeaderSecurity=void 0,()=>{delete window.debugHeaderSecurity}),[o,d,i,a,k,_]);let A=k?k.color==="green"?"var(--sb-green)":k.color==="orange"?"var(--sb-orange)":k.color==="yellow"?"var(--sb-yellow)":"var(--sb-red)":"var(--sb-green)",x=i?"var(--sb-green-solid)":["connecting","verifying","retrying","reconnecting"].includes(s)?"var(--sb-yellow-solid)":s==="failed"?"var(--sb-red-solid)":"var(--sb-text-9)",T=x==="var(--sb-green-solid)"?"rgba(var(--sb-green-rgb), 0.16)":x==="var(--sb-yellow-solid)"?"rgba(var(--sb-yellow-rgb), 0.16)":x==="var(--sb-red-solid)"?"rgba(var(--sb-red-rgb), 0.16)":"rgba(var(--sb-text-9-rgb), 0.16)",Y="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",B=!i,[R,ue]=React.useState(!1);React.useEffect(()=>{let G=()=>ue((window.scrollY||window.pageYOffset||0)>8);return G(),window.addEventListener("scroll",G,{passive:!0}),()=>window.removeEventListener("scroll",G)},[]);let K="blur(20px) saturate(180%)",O="background .25s ease, backdrop-filter .25s ease, -webkit-backdrop-filter .25s ease, border-color .25s ease",Q={paddingTop:"var(--sb-safe-top, 0px)",paddingBottom:"var(--sb-bar-extra, 0px)"},J={position:"fixed",top:0,left:0,right:0,...Q},ge=B?R?{...J,background:"rgba(var(--sb-bg-rgb), 0.72)",backdropFilter:K,WebkitBackdropFilter:K,borderBottom:"1px solid rgba(var(--sb-ink), 0.06)",transition:O}:{...J,background:"transparent",backdropFilter:"blur(0px) saturate(100%)",WebkitBackdropFilter:"blur(0px) saturate(100%)",borderBottom:"1px solid transparent",transition:O}:{...Q,background:"rgba(var(--sb-bg-rgb), 0.72)",backdropFilter:K,WebkitBackdropFilter:K,borderBottom:"1px solid rgba(var(--sb-ink), 0.06)"};return React.createElement("header",{className:B?"header-minimal z-50":"header-minimal sticky top-0 z-50",style:ge},[React.createElement("div",{key:"container",className:"max-w-7xl mx-auto",style:{padding:"0 20px"}},[React.createElement("div",{key:"content",className:"flex items-center justify-between",style:{height:"var(--sb-bar-h, 64px)",gap:"16px"}},[React.createElement("div",{key:"left",style:{display:"flex",alignItems:"center",gap:"12px",minWidth:0}},[React.createElement("div",{key:"logo",style:{width:"36px",height:"36px",flex:"none",display:"grid",placeItems:"center"}},React.createElement("img",{src:"/logo/securebit-mark.svg",alt:"SecureBit",style:{width:"100%",height:"100%",objectFit:"contain",display:"block"}})),React.createElement("div",{key:"txt",style:{lineHeight:1.2,minWidth:0}},[React.createElement("div",{key:"r1",style:{display:"flex",alignItems:"baseline",gap:"7px"}},[React.createElement("span",{key:"n",style:{fontSize:"16px",fontWeight:800,letterSpacing:"-0.3px",color:"var(--sb-text-2)"}},"SecureBit"),React.createElement("span",{key:"v",style:{fontFamily:Y,fontSize:"10px",fontWeight:500,color:"var(--sb-text-faint)"}},Ma)]),React.createElement("div",{key:"r2",className:"hidden sm:block",style:{fontSize:"11px",color:"var(--sb-text-9)",fontWeight:500}},f("hdr.tagline"))])]),React.createElement("div",{key:"right",style:{display:"flex",alignItems:"center",gap:"9px"}},[B&&React.createElement(Hr,{key:"lang"}),B&&React.createElement(Gr,{key:"theme"}),!B&&React.createElement("button",{key:"net",type:"button",onClick:()=>window.dispatchEvent(new CustomEvent("securebit:open-network-settings")),title:f("hdr.netSettingsTitle"),"aria-label":f("hdr.netSettings"),className:"sb-disconnect",style:{display:"grid",placeItems:"center",width:"38px",height:"38px",borderRadius:"9px",border:"1px solid rgba(var(--sb-ink), 0.07)",background:"rgba(var(--sb-ink), 0.02)",color:"var(--sb-text-6)",cursor:"pointer",transition:"all .15s"}},React.createElement("i",{className:"fas fa-network-wired",style:{fontSize:"13px"}})),!B&&k&&React.createElement("div",{key:"sec",onClick:w,onContextMenu:G=>{G.preventDefault(),typeof r=="function"&&r()},title:_.tooltip,className:"sb-secpill",style:{display:"flex",alignItems:"center",gap:"8px",padding:"7px 12px",borderRadius:"9px",border:"1px solid rgba(var(--sb-ink), 0.07)",background:"rgba(var(--sb-ink), 0.02)",cursor:"pointer"}},[React.createElement("i",{key:"i",className:"fas fa-shield-halved",style:{fontSize:"13px",color:A}}),React.createElement("span",{key:"l",className:"hidden sm:inline",style:{fontSize:"12.5px",fontWeight:600,color:"var(--sb-text-2)"}},f(`secLevel.${k.level}`)===`secLevel.${k.level}`?String(k.level):f(`secLevel.${k.level}`)),React.createElement("span",{key:"s",style:{fontFamily:Y,fontSize:"11.5px",color:"var(--sb-text-7)"}},k.score+"%")]),!B&&React.createElement("div",{key:"status",style:{display:"flex",alignItems:"center",gap:"8px",padding:"8px 13px",borderRadius:"9px",border:"1px solid rgba(var(--sb-ink), 0.07)",background:"rgba(var(--sb-ink), 0.02)"}},[React.createElement("span",{key:"dot",style:{width:"7px",height:"7px",borderRadius:"50%",background:x,boxShadow:"0 0 0 3px "+T}}),React.createElement("span",{key:"t",className Line truncated
`).map(r=>r.trim()).filter(Boolean).map(r=>({urls:r}));return Rs(t)}function Ps(n){return Array.isArray(n)?n.some(e=>(Array.isArray(e?.urls)?e.urls:[e?.urls]).some(Ls)):!1}var Ve=window.React,Oa=["# One URL per line, e.g.:","stun:stun.example.com:3478","turn:turn.example.com:3478?transport=udp","","# Or paste JSON for servers with credentials:",'[{"urls":"turns:turn.example.com:5349","username":"user","credential":"secret"}]'].join(` `).map(r=>r.trim()).filter(Boolean).map(r=>({urls:r}));return Nn(t)}function Vn(s){return Array.isArray(s)?s.some(e=>(Array.isArray(e?.urls)?e.urls:[e?.urls]).some(Un)):!1}var Ve=window.React,$a=["# One URL per line, e.g.:","stun:stun.example.com:3478","turn:turn.example.com:3478?transport=udp","","# Or paste JSON for servers with credentials:",'[{"urls":"turns:turn.example.com:5349","username":"user","credential":"secret"}]'].join(`
`);async function Ua(n,e=6e3){let t={host:0,srflx:0,relay:0};if(typeof RTCPeerConnection>"u")return{...t,error:f("ice.errUnavailable")};let r;try{r=new RTCPeerConnection({iceServers:n})}catch(i){return{...t,error:i.message||f("ice.errInvalid")}}return new Promise(i=>{let s=!1,a=()=>{if(!s){s=!0,clearTimeout(o);try{r.close()}catch{}i(t)}},o=setTimeout(a,e);r.onicecandidate=c=>{if(!c.candidate){a();return}let d=c.candidate.candidate||"";/ typ host/.test(d)?t.host++:/ typ srflx/.test(d)?t.srflx++:/ typ relay/.test(d)&&t.relay++};try{r.createDataChannel("securebit-ice-test"),r.createOffer().then(c=>r.setLocalDescription(c)).catch(()=>a())}catch{a()}})}var za=({isOpen:n,onClose:e,initial:t,hasSaved:r,onApply:i,onForget:s,embedded:a})=>{if(!n)return null;let[o,c]=Ve.useState(t?.useCustom||!1),[d,u]=Ve.useState(t?.serversText||""),[h,m]=Ve.useState(t?.privacyMode==="relay-only"),[p,S]=Ve.useState(t?.persisted||!1),[g,_]=Ve.useState("idle"),[w,M]=Ve.useState(null),F=o?Ds(d):{servers:[],errors:[],warnings:[]},k=Ps(F.servers),v=!o||F.servers.length>0&&F.errors.length===0,b=async()=>{_("running"),M(null);let N=await Ua(F.servers);M(N),_("done")},A=()=>{v&&i({useCustom:o,servers:o?F.servers:[],privacyMode:h?"relay-only":"standard",serversText:d},p)},x=async()=>{s&&await s(),S(!1)},T=Ve.createElement,j="var(--sb-orange)",z="var(--sb-green)",R="var(--sb-orange-solid)",fe="var(--sb-green-solid)",re="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",pe=(N,I,D,K,O)=>T("button",{type:"button",onClick:I,style:Object.assign({width:"100%",textAlign:"start",display:"flex",alignItems:"flex-start",gap:"12px",padding:"14px 15px",borderRadius:"13px",border:`1px solid ${N?"rgba(var(--sb-orange-rgb), 0.45)":"rgba(var(--sb-ink), 0.07)"}`,background:N?"rgba(var(--sb-orange-rgb), 0.06)":"var(--sb-surface)",color:"inherit",fontFamily:"inherit",cursor:"pointer",transition:"all .15s",marginBottom:"10px"},O||{})},[T("span",{key:"ring",style:{flex:"none",width:"18px",height:"18px",marginTop:"1px",borderRadius:"50%",border:`1.5px solid ${N?j:"rgba(var(--sb-ink), 0.22)"}`,display:"grid",placeItems:"center"}},T("span",{style:{width:"8px",height:"8px",borderRadius:"50%",background:N?R:"transparent"}})),T("span",{key:"tx",style:{flex:1}},[T("span",{key:"t",style:{display:"block",fontSize:"14px",fontWeight:700,color:"var(--sb-text-1)"}},D),T("span",{key:"d",style:{display:"block",fontSize:"12.5px",color:"var(--sb-text-7)",marginTop:"2px"}},K)])]),me=(N,I,D,K,O,q)=>T("button",{type:"button",onClick:I,style:{width:"100%",textAlign:"start",display:"flex",alignItems:"flex-start",gap:"12px",padding:"14px 15px",borderRadius:"13px",border:`1px solid ${N?"rgba(var(--sb-green-rgb), 0.3)":"rgba(var(--sb-ink), 0.07)"}`,background:N?"rgba(var(--sb-green-rgb), 0.05)":"var(--sb-surface)",color:"inherit",fontFamily:"inherit",cursor:"pointer",transition:"all .15s",marginBottom:"10px"}},[T("span",{key:"tx",style:{flex:1}},[T("span",{key:"r1",style:{display:"flex",alignItems:"center",gap:"8px"}},[T("span",{key:"t",style:{fontSize:"14px",fontWeight:700,color:"var(--sb-text-1)"}},D),q&&T("span",{key:"b",style:{fontSize:"10px",fontWeight:700,color:z,padding:"2px 7px",borderRadius:"5px",background:"rgba(var(--sb-green-rgb), 0.1)",border:"1px solid rgba(var(--sb-green-rgb), 0.22)"}},q)]),T("span",{key:"d",style:{display:"block",fontSize:"12.5px",lineHeight:1.5,color:"var(--sb-text-7)",marginTop:"3px"}},K)]),T("span",{key:"tr",style:{flex:"none",width:"42px",height:"24px",borderRadius:"99px",background:N?O||fe:"rgba(var(--sb-ink), 0.08)",border:`1px solid ${N?O||z:"rgba(var(--sb-ink), 0.12)"}`,position:"relative",transition:"all .18s",marginTop:"1px"}},T("span",{style:{position:"absolute",top:"2px",insetInlineStart:"2px",width:"18px",height:"18px",borderRadius:"50%",background:"#fff",transform:N?`translateX(${18*Oi()}px)`:"translateX(0)",transition:"transform .18s"}}))]),ce=[];if(ce.push(T("p",{key:"intro",style:{margin:"0 0 18px",fontSize:"13.5px",lineHeight:1.6,color:"var(--sb-text-6)"}},f("ice.intro"))),ce.push(pe(!o,()=>c(!1),f("ice.publicTitle"),f("ice.publicDesc"))),ce.push(pe(o,()=>c(!0),f("ice.customTitle"),f("ice.customDesc",{max:ze.MAX_SERVERS}),o?{marginBottom:"14px"}:null)),o){let N=[];N.push(T("div",{key:"ta",style:{borderRadius:"13px",border:"1px solid rgba(var(--sb-ink), 0.08)",background:"var(--sb-bg-deep)",overflow:"hidden",marginBottom:"12px"}},T("textarea",{dir:"ltr",value:d,onChange:D=>u(D.target.value),rows:5,spellCheck:!1,autoComplete:"off",placeholder:Oa,style:{width:"100%",resize:"vertical",border:"none",outline:"none",background:"transparent",color:"var(--sb-text-code)",fontFamily:re,fontSize:"12px",lineHeight:1.65,padding:"13px 14px",minHeight:"104px"}}))),F.errors.length>0&&N.push(T("ul",{key:"err",style:{margin:"0 0 10px",paddingInlineStart:"18px",color:"var(--sb-red)",fontSize:"12.5px"}},F.errors.slice(0,6).map((D,K)=>T("li",{key:K},D)))),F.warnings.length>0&&N.push(T("ul",{key:"warn",style:{margin:"0 0 10px",paddingInlineStar Line truncated `);async function Ga(s,e=6e3){let t={host:0,srflx:0,relay:0};if(typeof RTCPeerConnection>"u")return{...t,error:f("ice.errUnavailable")};let r;try{r=new RTCPeerConnection({iceServers:s})}catch(i){return{...t,error:i.message||f("ice.errInvalid")}}return new Promise(i=>{let n=!1,a=()=>{if(!n){n=!0,clearTimeout(o);try{r.close()}catch{}i(t)}},o=setTimeout(a,e);r.onicecandidate=c=>{if(!c.candidate){a();return}let d=c.candidate.candidate||"";/ typ host/.test(d)?t.host++:/ typ srflx/.test(d)?t.srflx++:/ typ relay/.test(d)&&t.relay++};try{r.createDataChannel("securebit-ice-test"),r.createOffer().then(c=>r.setLocalDescription(c)).catch(()=>a())}catch{a()}})}var qa=({isOpen:s,onClose:e,initial:t,hasSaved:r,onApply:i,onForget:n,embedded:a})=>{if(!s)return null;let[o,c]=Ve.useState(t?.useCustom||!1),[d,u]=Ve.useState(t?.serversText||""),[h,m]=Ve.useState(t?.privacyMode==="relay-only"),[p,S]=Ve.useState(t?.persisted||!1),[g,b]=Ve.useState("idle"),[w,M]=Ve.useState(null),F=o?zn(d):{servers:[],errors:[],warnings:[]},k=Vn(F.servers),E=!o||F.servers.length>0&&F.errors.length===0,_=async()=>{b("running"),M(null);let U=await Ga(F.servers);M(U),b("done")},A=()=>{E&&i({useCustom:o,servers:o?F.servers:[],privacyMode:h?"relay-only":"standard",serversText:d},p)},x=async()=>{n&&await n(),S(!1)},T=Ve.createElement,Y="var(--sb-orange)",B="var(--sb-green)",R="var(--sb-orange-solid)",ue="var(--sb-green-solid)",K="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",O=(U,I,D,N,z)=>T("button",{type:"button",onClick:I,style:Object.assign({width:"100%",textAlign:"start",display:"flex",alignItems:"flex-start",gap:"12px",padding:"14px 15px",borderRadius:"13px",border:`1px solid ${U?"rgba(var(--sb-orange-rgb), 0.45)":"rgba(var(--sb-ink), 0.07)"}`,background:U?"rgba(var(--sb-orange-rgb), 0.06)":"var(--sb-surface)",color:"inherit",fontFamily:"inherit",cursor:"pointer",transition:"all .15s",marginBottom:"10px"},z||{})},[T("span",{key:"ring",style:{flex:"none",width:"18px",height:"18px",marginTop:"1px",borderRadius:"50%",border:`1.5px solid ${U?Y:"rgba(var(--sb-ink), 0.22)"}`,display:"grid",placeItems:"center"}},T("span",{style:{width:"8px",height:"8px",borderRadius:"50%",background:U?R:"transparent"}})),T("span",{key:"tx",style:{flex:1}},[T("span",{key:"t",style:{display:"block",fontSize:"14px",fontWeight:700,color:"var(--sb-text-1)"}},D),T("span",{key:"d",style:{display:"block",fontSize:"12.5px",color:"var(--sb-text-7)",marginTop:"2px"}},N)])]),Q=(U,I,D,N,z,W)=>T("button",{type:"button",onClick:I,style:{width:"100%",textAlign:"start",display:"flex",alignItems:"flex-start",gap:"12px",padding:"14px 15px",borderRadius:"13px",border:`1px solid ${U?"rgba(var(--sb-green-rgb), 0.3)":"rgba(var(--sb-ink), 0.07)"}`,background:U?"rgba(var(--sb-green-rgb), 0.05)":"var(--sb-surface)",color:"inherit",fontFamily:"inherit",cursor:"pointer",transition:"all .15s",marginBottom:"10px"}},[T("span",{key:"tx",style:{flex:1}},[T("span",{key:"r1",style:{display:"flex",alignItems:"center",gap:"8px"}},[T("span",{key:"t",style:{fontSize:"14px",fontWeight:700,color:"var(--sb-text-1)"}},D),W&&T("span",{key:"b",style:{fontSize:"10px",fontWeight:700,color:B,padding:"2px 7px",borderRadius:"5px",background:"rgba(var(--sb-green-rgb), 0.1)",border:"1px solid rgba(var(--sb-green-rgb), 0.22)"}},W)]),T("span",{key:"d",style:{display:"block",fontSize:"12.5px",lineHeight:1.5,color:"var(--sb-text-7)",marginTop:"3px"}},N)]),T("span",{key:"tr",style:{flex:"none",width:"42px",height:"24px",borderRadius:"99px",background:U?z||ue:"rgba(var(--sb-ink), 0.08)",border:`1px solid ${U?z||B:"rgba(var(--sb-ink), 0.12)"}`,position:"relative",transition:"all .18s",marginTop:"1px"}},T("span",{style:{position:"absolute",top:"2px",insetInlineStart:"2px",width:"18px",height:"18px",borderRadius:"50%",background:"#fff",transform:U?`translateX(${18*$i()}px)`:"translateX(0)",transition:"transform .18s"}}))]),J=[];if(J.push(T("p",{key:"intro",style:{margin:"0 0 18px",fontSize:"13.5px",lineHeight:1.6,color:"var(--sb-text-6)"}},f("ice.intro"))),J.push(O(!o,()=>c(!1),f("ice.publicTitle"),f("ice.publicDesc"))),J.push(O(o,()=>c(!0),f("ice.customTitle"),f("ice.customDesc",{max:ze.MAX_SERVERS}),o?{marginBottom:"14px"}:null)),o){let U=[];U.push(T("div",{key:"ta",style:{borderRadius:"13px",border:"1px solid rgba(var(--sb-ink), 0.08)",background:"var(--sb-bg-deep)",overflow:"hidden",marginBottom:"12px"}},T("textarea",{dir:"ltr",value:d,onChange:D=>u(D.target.value),rows:5,spellCheck:!1,autoComplete:"off",placeholder:$a,style:{width:"100%",resize:"vertical",border:"none",outline:"none",background:"transparent",color:"var(--sb-text-code)",fontFamily:K,fontSize:"12px",lineHeight:1.65,padding:"13px 14px",minHeight:"104px"}}))),F.errors.length>0&&U.push(T("ul",{key:"err",style:{margin:"0 0 10px",paddingInlineStart:"18px",color:"var(--sb-red)",fontSize:"12.5px"}},F.errors.slice(0,6).map((D,N)=>T("li",{key:N},D)))),F.warnings.length>0&&U.push(T("ul",{key:"warn",style:{margin:"0 0 10px",paddingInlineStart:"18px",c Line truncated
/** /**
* Secure and Reliable Notification Manager for P2P WebRTC Chat * Secure and Reliable Notification Manager for P2P WebRTC Chat
* Follows best practices: OWASP, MDN, Chrome DevRel * Follows best practices: OWASP, MDN, Chrome DevRel
+4 -4
View File
@@ -1,7 +1,7 @@
{ {
"version": 3, "version": 3,
"sources": ["../src/notifications/SecureNotificationManager.js", "../src/notifications/NotificationIntegration.js", "../node_modules/dompurify/src/utils.ts", "../node_modules/dompurify/src/tags.ts", "../node_modules/dompurify/src/attrs.ts", "../node_modules/dompurify/src/regexp.ts", "../node_modules/dompurify/src/purify.ts", "../src/crypto/EnhancedSecureCryptoUtils.js", "../src/i18n/generated.js", "../src/i18n/dict/en.js", "../src/i18n/index.js", "../src/transfer/EnhancedSecureFileTransfer.js", "../src/network/webrtc/config.js", "../src/network/webrtc/sdp.js", "../src/network/webrtc/audio.js", "../src/network/webrtc/video.js", "../src/network/webrtc/adaptation/metrics.js", "../src/network/webrtc/adaptation/controller.js", "../src/crypto/DoubleRatchet.js", "../src/network/descriptor/sbq2.js", "../src/network/descriptor/keyexchange.js", "../src/network/EnhancedSecureWebRTCManager.js", "../src/scripts/app-boot.js", "../package.json", "../src/components/ui/LanguageSwitcher.jsx", "../src/components/ui/ThemeSwitcher.jsx", "../src/components/ui/Header.jsx", "../src/components/ui/DownloadApps.jsx", "../src/components/ui/BecomePartner.jsx", "../src/components/ui/UniqueFeatureSlider.jsx", "../src/components/ui/Roadmap.jsx", "../src/components/ui/CommunityCTA.jsx", "../src/components/ui/FileTransfer.jsx", "../src/network/iceServers.js", "../src/components/ui/IceServerSettings.jsx", "../src/components/ui/CallUI.jsx"], "sources": ["../src/notifications/SecureNotificationManager.js", "../src/notifications/NotificationIntegration.js", "../node_modules/dompurify/src/utils.ts", "../node_modules/dompurify/src/tags.ts", "../node_modules/dompurify/src/attrs.ts", "../node_modules/dompurify/src/regexp.ts", "../node_modules/dompurify/src/purify.ts", "../src/crypto/EnhancedSecureCryptoUtils.js", "../src/i18n/generated.js", "../src/i18n/dict/en.js", "../src/i18n/index.js", "../src/transfer/EnhancedSecureFileTransfer.js", "../src/network/webrtc/config.js", "../src/network/webrtc/sdp.js", "../src/network/webrtc/audio.js", "../src/network/webrtc/video.js", "../src/network/webrtc/adaptation/metrics.js", "../src/network/webrtc/adaptation/controller.js", "../src/crypto/DoubleRatchet.js", "../src/network/descriptor/sbq2.js", "../src/network/descriptor/keyexchange.js", "../src/network/EnhancedSecureWebRTCManager.js", "../src/scripts/app-boot.js", "../package.json", "../src/components/ui/LanguageSwitcher.jsx", "../src/components/ui/ThemeSwitcher.jsx", "../src/components/ui/Header.jsx", "../src/components/ui/DownloadApps.jsx", "../src/components/ui/BecomePartner.jsx", "../src/components/ui/UniqueFeatureSlider.jsx", "../src/components/ui/Roadmap.jsx", "../src/components/ui/DonateCrypto.jsx", "../src/components/ui/CommunityCTA.jsx", "../src/components/ui/FileTransfer.jsx", "../src/network/iceServers.js", "../src/components/ui/IceServerSettings.jsx", "../src/components/ui/CallUI.jsx"],
"sourcesContent": ["/**\n * Secure and Reliable Notification Manager for P2P WebRTC Chat\n * Follows best practices: OWASP, MDN, Chrome DevRel\n * \n * @version 1.0.0\n * @author SecureBit Team\n * @license MIT\n */\n\nclass SecureChatNotificationManager {\n constructor(config = {}) {\n // Safely read Notification permission (iOS Safari may not define Notification)\n this.permission = (typeof Notification !== 'undefined' && Notification && typeof Notification.permission === 'string')\n ? Notification.permission\n : 'denied';\n this.isTabActive = this.checkTabActive(); // Initialize with proper check\n this.unreadCount = 0;\n this.originalTitle = document.title;\n this.notificationQueue = [];\n this.maxQueueSize = config.maxQueueSize || 5;\n this.rateLimitMs = config.rateLimitMs || 2000; // Spam protection\n this.lastNotificationTime = 0;\n this.trustedOrigins = config.trustedOrigins || [];\n \n // Secure context flag\n this.isSecureContext = window.isSecureContext;\n \n // Cross-browser compatibility for Page Visibility API\n this.hidden = this.getHiddenProperty();\n this.visibilityChange = this.getVisibilityChangeEvent();\n \n this.initVisibilityTracking();\n this.initSecurityChecks();\n }\n\n /**\n * Initialize security checks and validation\n * @private\n */\n initSecurityChecks() {\n // Security checks are performed silently\n }\n\n /**\n * Get hidden property name for cross-browser compatibility\n * @returns {string} Hidden property name\n * @private\n */\n getHiddenProperty() {\n if (typeof document.hidden !== \"undefined\") {\n return \"hidden\";\n } else if (typeof document.msHidden !== \"undefined\") {\n return \"msHidden\";\n } else if (typeof document.webkitHidden !== \"undefined\") {\n return \"webkitHidden\";\n }\n return \"hidden\"; // fallback\n }\n\n /**\n * Get visibility change event name for cross-browser compatibility\n * @returns {string} Visibility change event name\n * @private\n */\n getVisibilityChangeEvent() {\n if (typeof document.hidden !== \"undefined\") {\n return \"visibilitychange\";\n } else if (typeof document.msHidden !== \"undefined\") {\n return \"msvisibilitychange\";\n } else if (typeof document.webkitHidden !== \"undefined\") {\n return \"webkitvisibilitychange\";\n }\n return \"visibilitychange\"; // fallback\n }\n\n /**\n * Check if tab is currently active using multiple methods\n * @returns {boolean} True if tab is active\n * @private\n */\n checkTabActive() {\n // Primary method: Page Visibility API\n if (this.hidden && typeof document[this.hidden] !== \"undefined\") {\n return !document[this.hidden];\n }\n \n // Fallback method: document.hasFocus()\n if (typeof document.hasFocus === \"function\") {\n return document.hasFocus();\n }\n \n // Ultimate fallback: assume active\n return true;\n }\n\n /**\n * Initialize page visibility tracking (Page Visibility API)\n * @private\n */\n initVisibilityTracking() {\n // Primary method: Page Visibility API with cross-browser support\n if (typeof document.addEventListener !== \"undefined\" && typeof document[this.hidden] !== \"undefined\") {\n document.addEventListener(this.visibilityChange, () => {\n this.isTabActive = this.checkTabActive();\n \n if (this.isTabActive) {\n this.resetUnreadCount();\n this.clearNotificationQueue();\n }\n });\n }\n\n // Fallback method: Window focus/blur events\n window.addEventListener('focus', () => {\n this.isTabActive = this.checkTabActive();\n if (this.isTabActive) {\n this.resetUnreadCount();\n }\n });\n\n window.addEventListener('blur', () => {\n this.isTabActive = this.checkTabActive();\n });\n\n // Page unload cleanup\n window.addEventListener('beforeunload', () => {\n this.clearNotificationQueue();\n });\n }\n\n /**\n * Request notification permission (BEST PRACTICE: Only call in response to user action)\n * Never call on page load!\n * @returns {Promise<boolean>} Permission granted status\n */\n async requestPermission() {\n // Secure context check\n if (!this.isSecureContext || !('Notification' in window)) {\n return false;\n }\n\n if (this.permission === 'granted') {\n return true;\n }\n\n if (this.permission === 'denied') {\n return false;\n }\n\n try {\n this.permission = await Notification.requestPermission();\n return this.permission === 'granted';\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Update page title with unread count\n * @private\n */\n updateTitle() {\n if (this.unreadCount > 0) {\n document.title = `(${this.unreadCount}) ${this.originalTitle}`;\n } else {\n document.title = this.originalTitle;\n }\n }\n\n /**\n Line truncated "sourcesContent": ["/**\n * Secure and Reliable Notification Manager for P2P WebRTC Chat\n * Follows best practices: OWASP, MDN, Chrome DevRel\n * \n * @version 1.0.0\n * @author SecureBit Team\n * @license MIT\n */\n\nclass SecureChatNotificationManager {\n constructor(config = {}) {\n // Safely read Notification permission (iOS Safari may not define Notification)\n this.permission = (typeof Notification !== 'undefined' && Notification && typeof Notification.permission === 'string')\n ? Notification.permission\n : 'denied';\n this.isTabActive = this.checkTabActive(); // Initialize with proper check\n this.unreadCount = 0;\n this.originalTitle = document.title;\n this.notificationQueue = [];\n this.maxQueueSize = config.maxQueueSize || 5;\n this.rateLimitMs = config.rateLimitMs || 2000; // Spam protection\n this.lastNotificationTime = 0;\n this.trustedOrigins = config.trustedOrigins || [];\n \n // Secure context flag\n this.isSecureContext = window.isSecureContext;\n \n // Cross-browser compatibility for Page Visibility API\n this.hidden = this.getHiddenProperty();\n this.visibilityChange = this.getVisibilityChangeEvent();\n \n this.initVisibilityTracking();\n this.initSecurityChecks();\n }\n\n /**\n * Initialize security checks and validation\n * @private\n */\n initSecurityChecks() {\n // Security checks are performed silently\n }\n\n /**\n * Get hidden property name for cross-browser compatibility\n * @returns {string} Hidden property name\n * @private\n */\n getHiddenProperty() {\n if (typeof document.hidden !== \"undefined\") {\n return \"hidden\";\n } else if (typeof document.msHidden !== \"undefined\") {\n return \"msHidden\";\n } else if (typeof document.webkitHidden !== \"undefined\") {\n return \"webkitHidden\";\n }\n return \"hidden\"; // fallback\n }\n\n /**\n * Get visibility change event name for cross-browser compatibility\n * @returns {string} Visibility change event name\n * @private\n */\n getVisibilityChangeEvent() {\n if (typeof document.hidden !== \"undefined\") {\n return \"visibilitychange\";\n } else if (typeof document.msHidden !== \"undefined\") {\n return \"msvisibilitychange\";\n } else if (typeof document.webkitHidden !== \"undefined\") {\n return \"webkitvisibilitychange\";\n }\n return \"visibilitychange\"; // fallback\n }\n\n /**\n * Check if tab is currently active using multiple methods\n * @returns {boolean} True if tab is active\n * @private\n */\n checkTabActive() {\n // Primary method: Page Visibility API\n if (this.hidden && typeof document[this.hidden] !== \"undefined\") {\n return !document[this.hidden];\n }\n \n // Fallback method: document.hasFocus()\n if (typeof document.hasFocus === \"function\") {\n return document.hasFocus();\n }\n \n // Ultimate fallback: assume active\n return true;\n }\n\n /**\n * Initialize page visibility tracking (Page Visibility API)\n * @private\n */\n initVisibilityTracking() {\n // Primary method: Page Visibility API with cross-browser support\n if (typeof document.addEventListener !== \"undefined\" && typeof document[this.hidden] !== \"undefined\") {\n document.addEventListener(this.visibilityChange, () => {\n this.isTabActive = this.checkTabActive();\n \n if (this.isTabActive) {\n this.resetUnreadCount();\n this.clearNotificationQueue();\n }\n });\n }\n\n // Fallback method: Window focus/blur events\n window.addEventListener('focus', () => {\n this.isTabActive = this.checkTabActive();\n if (this.isTabActive) {\n this.resetUnreadCount();\n }\n });\n\n window.addEventListener('blur', () => {\n this.isTabActive = this.checkTabActive();\n });\n\n // Page unload cleanup\n window.addEventListener('beforeunload', () => {\n this.clearNotificationQueue();\n });\n }\n\n /**\n * Request notification permission (BEST PRACTICE: Only call in response to user action)\n * Never call on page load!\n * @returns {Promise<boolean>} Permission granted status\n */\n async requestPermission() {\n // Secure context check\n if (!this.isSecureContext || !('Notification' in window)) {\n return false;\n }\n\n if (this.permission === 'granted') {\n return true;\n }\n\n if (this.permission === 'denied') {\n return false;\n }\n\n try {\n this.permission = await Notification.requestPermission();\n return this.permission === 'granted';\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Update page title with unread count\n * @private\n */\n updateTitle() {\n if (this.unreadCount > 0) {\n document.title = `(${this.unreadCount}) ${this.originalTitle}`;\n } else {\n document.title = this.originalTitle;\n }\n }\n\n /**\n Line truncated
"mappings": "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 truncated "mappings": "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 truncated
"names": ["require_SecureNotificationManager", "__commonJSMin", "exports", "module", "SecureChatNotificationManager", "config", "text", "div", "url", "parsedUrl", "origin", "now", "senderName", "message", "options", "safeSenderName", "safeMessage", "safeIcon", "notification", "event", "error", "autoCloseTimeout", "index", "SecureP2PChat", "dataChannel", "data", "senderId", "container", "messageEl", "nameEl", "textEl", "timeEl", "audio", "require_NotificationIntegration", "__commonJSMin", "exports", "module", "import_SecureNotificationManager", "NotificationIntegration", "webrtcManager", "message", "type", "rest", "status", "meta", "messageKey", "messagesArray", "msg", "messageInfo", "notificationText", "notificationResult", "senderId", "messageData", "messagesContainer", "entries", "Object", "setPrototypeOf", "isFrozen", "getPrototypeOf", "getOwnPropertyDescriptor", "freeze", "seal", "create", "_ref", "Reflect", "apply", "construct", "x", "func", "thisArg", "_len", "arguments", "length", "args", "Array", "_key", "Func", "_len2", "_key2", "arrayForEach", "unapply", "prototype", "forEach", "arrayLastIndexOf", "lastIndexOf", "arrayPop", "pop", "arrayPush", "push", "arraySplice", "splice", "arrayIsArray", "isArray", "stringToLowerCase", "String", "toLowerCase", "stringToString", "toString", "stringMatch", "match", "stringReplace", "replace", "stringIndexOf", "indexOf", "stringTrim", "trim", "numberToString", "Number", "booleanToString", "Boolean", "bigintToString", "BigInt", "symbolToString", "Symbol", "objectHasOwnProperty", "hasOwnProperty", "objectToString", "regExpTest", "RegExp", "test", "typeErrorCreate", "unconstruct", "TypeError", "lastIndex", "_len3", "_key3", "_len4", "_key4", "addToSet", "set", "array", "transformCaseFunc", "l", "element", "lcElement", "cleanArray", "index", "clone", "object", "newObject", "_ref2", "_ref3", "_slicedToArray", "property", "value", "constructor", "stringifyValue", "valueAsRecord", "valueToString", "lookupGetter", "stringified", "prop", "desc", "get", "fallbackValue", "isRegex", "html", "svg", "svgFilters", "svgDisallowed", "mathMl", "mathMlDisallowed", "text", "xml", "MUSTACHE_EXPR", "ERB_EXPR", "TMPLIT_EXPR", "DATA_ATTR", "ARIA_ATTR", "IS_ALLOWED_URI", "IS_SCRIPT_OR_DATA", "ATTR_WHITESPACE", "DOCTYPE_NAME", "CUSTOM_ELEMENT", "ELEMENT_MARKUP_PROBE", "COMMENT_MARKUP_PROBE", "FALLBACK_TAG_CLOSE", "SELF_CLOSING_TAG", "NODE_TYPE", "attribute", "cdataSection", "entityReference", "entityNode", "processingInstruction", "comment", "document", "documentType", "documentFragment", "notation", "getGlobal", "window", "_createTrustedTypesPolicy", "trustedTypes", "purifyHostElement", "createPolicy", "suffix", "ATTR_NAME", "hasAttribute", "getAttribute", "policyName", "createHTML", "createScriptURL", "scriptUrl", "console", "warn", "_createHooksMap", "afterSanitizeAttributes", "afterSanitizeElements", "afterSanitizeShadowDOM", "beforeSanitizeAttributes", "beforeSanitizeElements", "beforeSanitizeShadowDOM", "uponSanitizeAttribute", "uponSanitizeElement", "uponSanitizeShadowNode", "_resolveSetOption", "cfg", "key", "fallback", "options", "base", "transform", "createDOMPurify", "undefined", "DOMPurify", "root", "version", "VERSION", "removed", "nodeType", "Element", "isSupported", "originalDocument", "currentScript", "DocumentFragment", "HTMLTemplateElement", "Node", "NodeFilter", "_window$NamedNodeMap", "NamedNodeMap", "MozNamedAttrMap", "HTMLFormElement", "DOMParser", "ElementPrototype", "cloneNode", "remove", "getNextSibling", "getChildNodes", "getParentNode", "getShadowRoot", "getAttributes", "getNodeType", "getNodeName", "template", "createElement", "content", "ownerDocument", "trustedTypesPolicy", "emptyHTML", "defaultTrustedTypesPolicy", "defaultTrustedTypesPolicyResolved", "IN_TRUSTED_TYPES_POLICY", "_assertNotInTrustedTypesPolicy", "_createTrustedHTML", "_createTrustedScriptURL", "_getDefaultTrustedTypesPolicy", "_document", "implementation", "createNodeIterator", "createDocumentFragment", "getElementsByTagName", "importNode", "hooks", "createHTMLDocument", "EXPRESSIONS", "ALLOWED_TAGS", "DEFAULT_ALLOWED_TAGS", "TAGS", "ALLOWED_ATTR", "DEFAULT_ALLOWED_ATTR", "ATTRS", "CUSTOM_ELEMENT_HANDLING", "tagNameCheck", "writable", "configurable", "enumerable", "attributeNameCheck", "allowCustomizedBuiltInElements", "FORBID_TAGS", "FORBID_ATTR", "EXTRA_ELEMENT_HANDLING", "tagCheck", "attributeCheck", "ALLOW_ARIA_ATTR", "ALLOW_DATA_ATTR", "ALLOW_UNKNOWN_PROTOCOLS", "ALLOW_SELF_CLOSE_IN_ATTR", "SAFE_FOR_TEMPLATES", "SAFE_FOR_XML", "WHOLE_DOCUMENT", "SET_CONFIG", "FORCE_BODY", "RETURN_DOM", "RETURN_DOM_FRAGMENT", "RETURN_TRUSTED_TYPE", "SANITIZE_DOM", "SANITIZE_NAMED_PROPS", "SANITIZE_NAMED_PROPS_PREFIX", "KEEP_CONTENT", "IN_PLACE", "USE_PROFILES", "FORBID_CONTENTS", "DEFAULT_FORBID_CONTENTS", "DATA_URI_TAGS", "DEFAULT_DATA_URI_TAGS", "URI_SAFE_ATTRIBUTES", "DEFAULT_URI_SAFE_ATTRIBUTES", "MATHML_NAMESPACE", "SVG_NAMESPACE", "HTML_NAMESPACE", "NAMESPACE", "IS_EMPTY_INPUT", "ALLOWED_NAMESPACELine truncated "names": ["require_SecureNotificationManager", "__commonJSMin", "exports", "module", "SecureChatNotificationManager", "config", "text", "div", "url", "parsedUrl", "origin", "now", "senderName", "message", "options", "safeSenderName", "safeMessage", "safeIcon", "notification", "event", "error", "autoCloseTimeout", "index", "SecureP2PChat", "dataChannel", "data", "senderId", "container", "messageEl", "nameEl", "textEl", "timeEl", "audio", "require_NotificationIntegration", "__commonJSMin", "exports", "module", "import_SecureNotificationManager", "NotificationIntegration", "webrtcManager", "message", "type", "rest", "status", "meta", "messageKey", "messagesArray", "msg", "messageInfo", "notificationText", "notificationResult", "senderId", "messageData", "messagesContainer", "entries", "Object", "setPrototypeOf", "isFrozen", "getPrototypeOf", "getOwnPropertyDescriptor", "freeze", "seal", "create", "_ref", "Reflect", "apply", "construct", "x", "func", "thisArg", "_len", "arguments", "length", "args", "Array", "_key", "Func", "_len2", "_key2", "arrayForEach", "unapply", "prototype", "forEach", "arrayLastIndexOf", "lastIndexOf", "arrayPop", "pop", "arrayPush", "push", "arraySplice", "splice", "arrayIsArray", "isArray", "stringToLowerCase", "String", "toLowerCase", "stringToString", "toString", "stringMatch", "match", "stringReplace", "replace", "stringIndexOf", "indexOf", "stringTrim", "trim", "numberToString", "Number", "booleanToString", "Boolean", "bigintToString", "BigInt", "symbolToString", "Symbol", "objectHasOwnProperty", "hasOwnProperty", "objectToString", "regExpTest", "RegExp", "test", "typeErrorCreate", "unconstruct", "TypeError", "lastIndex", "_len3", "_key3", "_len4", "_key4", "addToSet", "set", "array", "transformCaseFunc", "l", "element", "lcElement", "cleanArray", "index", "clone", "object", "newObject", "_ref2", "_ref3", "_slicedToArray", "property", "value", "constructor", "stringifyValue", "valueAsRecord", "valueToString", "lookupGetter", "stringified", "prop", "desc", "get", "fallbackValue", "isRegex", "html", "svg", "svgFilters", "svgDisallowed", "mathMl", "mathMlDisallowed", "text", "xml", "MUSTACHE_EXPR", "ERB_EXPR", "TMPLIT_EXPR", "DATA_ATTR", "ARIA_ATTR", "IS_ALLOWED_URI", "IS_SCRIPT_OR_DATA", "ATTR_WHITESPACE", "DOCTYPE_NAME", "CUSTOM_ELEMENT", "ELEMENT_MARKUP_PROBE", "COMMENT_MARKUP_PROBE", "FALLBACK_TAG_CLOSE", "SELF_CLOSING_TAG", "NODE_TYPE", "attribute", "cdataSection", "entityReference", "entityNode", "processingInstruction", "comment", "document", "documentType", "documentFragment", "notation", "getGlobal", "window", "_createTrustedTypesPolicy", "trustedTypes", "purifyHostElement", "createPolicy", "suffix", "ATTR_NAME", "hasAttribute", "getAttribute", "policyName", "createHTML", "createScriptURL", "scriptUrl", "console", "warn", "_createHooksMap", "afterSanitizeAttributes", "afterSanitizeElements", "afterSanitizeShadowDOM", "beforeSanitizeAttributes", "beforeSanitizeElements", "beforeSanitizeShadowDOM", "uponSanitizeAttribute", "uponSanitizeElement", "uponSanitizeShadowNode", "_resolveSetOption", "cfg", "key", "fallback", "options", "base", "transform", "createDOMPurify", "undefined", "DOMPurify", "root", "version", "VERSION", "removed", "nodeType", "Element", "isSupported", "originalDocument", "currentScript", "DocumentFragment", "HTMLTemplateElement", "Node", "NodeFilter", "_window$NamedNodeMap", "NamedNodeMap", "MozNamedAttrMap", "HTMLFormElement", "DOMParser", "ElementPrototype", "cloneNode", "remove", "getNextSibling", "getChildNodes", "getParentNode", "getShadowRoot", "getAttributes", "getNodeType", "getNodeName", "template", "createElement", "content", "ownerDocument", "trustedTypesPolicy", "emptyHTML", "defaultTrustedTypesPolicy", "defaultTrustedTypesPolicyResolved", "IN_TRUSTED_TYPES_POLICY", "_assertNotInTrustedTypesPolicy", "_createTrustedHTML", "_createTrustedScriptURL", "_getDefaultTrustedTypesPolicy", "_document", "implementation", "createNodeIterator", "createDocumentFragment", "getElementsByTagName", "importNode", "hooks", "createHTMLDocument", "EXPRESSIONS", "ALLOWED_TAGS", "DEFAULT_ALLOWED_TAGS", "TAGS", "ALLOWED_ATTR", "DEFAULT_ALLOWED_ATTR", "ATTRS", "CUSTOM_ELEMENT_HANDLING", "tagNameCheck", "writable", "configurable", "enumerable", "attributeNameCheck", "allowCustomizedBuiltInElements", "FORBID_TAGS", "FORBID_ATTR", "EXTRA_ELEMENT_HANDLING", "tagCheck", "attributeCheck", "ALLOW_ARIA_ATTR", "ALLOW_DATA_ATTR", "ALLOW_UNKNOWN_PROTOCOLS", "ALLOW_SELF_CLOSE_IN_ATTR", "SAFE_FOR_TEMPLATES", "SAFE_FOR_XML", "WHOLE_DOCUMENT", "SET_CONFIG", "FORCE_BODY", "RETURN_DOM", "RETURN_DOM_FRAGMENT", "RETURN_TRUSTED_TYPE", "SANITIZE_DOM", "SANITIZE_NAMED_PROPS", "SANITIZE_NAMED_PROPS_PREFIX", "KEEP_CONTENT", "IN_PLACE", "USE_PROFILES", "FORBID_CONTENTS", "DEFAULT_FORBID_CONTENTS", "DATA_URI_TAGS", "DEFAULT_DATA_URI_TAGS", "URI_SAFE_ATTRIBUTES", "DEFAULT_URI_SAFE_ATTRIBUTES", "MATHML_NAMESPACE", "SVG_NAMESPACE", "HTML_NAMESPACE", "NAMESPACE", "IS_EMPTY_INPUT", "ALLOWED_NAMESPACELine truncated
} }
Vendored
+4 -4
View File
@@ -1,6 +1,6 @@
function Wa(t=globalThis.window){return t?.SECUREBIT_DEBUG===!0}function Bs({targetWindow:t=globalThis.window,webrtcManagerRef:e,onClearData:n,clearConsole:r=()=>{typeof console.clear=="function"&&console.clear()}}){return Wa(t)?(t.forceCleanup=()=>{n(),e.current&&e.current.disconnect()},t.clearLogs=r,t.webrtcManagerRef=e,()=>{delete t.forceCleanup,delete t.clearLogs,delete t.webrtcManagerRef}):()=>{}}var Va="securebit-net";var Hr="ice-device-key",Vr="ice-settings";function jr(){return typeof indexedDB<"u"&&typeof crypto<"u"&&!!crypto.subtle}function $r(){return new Promise((t,e)=>{let n;try{n=indexedDB.open(Va,1)}catch(r){e(r);return}n.onupgradeneeded=()=>{let r=n.result;r.objectStoreNames.contains("kv")||r.createObjectStore("kv")},n.onsuccess=()=>t(n.result),n.onerror=()=>e(n.error)})}function Wr(t,e){return new Promise((n,r)=>{let c=t.transaction("kv","readonly").objectStore("kv").get(e);c.onsuccess=()=>n(c.result),c.onerror=()=>r(c.error)})}function zs(t,e,n){return new Promise((r,i)=>{let c=t.transaction("kv","readwrite");c.objectStore("kv").put(n,e),c.oncomplete=()=>r(),c.onerror=()=>i(c.error)})}function ja(t,e){return new Promise((n,r)=>{let i=t.transaction("kv","readwrite");i.objectStore("kv").delete(e),i.oncomplete=()=>n(),i.onerror=()=>r(i.error)})}async function $a(t){let e=await Wr(t,Hr);if(e instanceof CryptoKey)return e;let n=await crypto.subtle.generateKey({name:"AES-GCM",length:256},!1,["encrypt","decrypt"]);return await zs(t,Hr,n),n}async function Us(t){if(!jr())throw new Error("Persistent storage is not available in this browser");let e=await $r(),n=await $a(e),r=JSON.stringify({version:1,servers:Array.isArray(t?.servers)?t.servers:[],privacyMode:t?.privacyMode==="relay-only"?"relay-only":"standard"}),i=crypto.getRandomValues(new Uint8Array(12)),c=await crypto.subtle.encrypt({name:"AES-GCM",iv:i},n,new TextEncoder().encode(r));await zs(e,Vr,{iv:Array.from(i),data:Array.from(new Uint8Array(c))})}async function Gs(){if(!jr())return null;try{let t=await $r(),e=await Wr(t,Vr);if(!e||!Array.isArray(e.iv)||!Array.isArray(e.data))return null;let n=await Wr(t,Hr);if(!(n instanceof CryptoKey))return null;let r=await crypto.subtle.decrypt({name:"AES-GCM",iv:new Uint8Array(e.iv)},n,new Uint8Array(e.data)),i=JSON.parse(new TextDecoder().decode(r));return{servers:Array.isArray(i.servers)?i.servers:[],privacyMode:i.privacyMode==="relay-only"?"relay-only":"standard"}}catch{return null}}async function qr(){if(jr())try{let t=await $r();await ja(t,Vr)}catch{}}var qa="/api/turn-credentials",Vs=["turn.securebit.chat","144.172.96.126"];var Hs=[6e4,3e5,9e5];function js(t){let e=/^turns?:([^:?\s]+)/i.exec(String(t||"").trim());return e?e[1].toLowerCase():null}function Ya(t){return!t||typeof t!="object"?!1:(Array.isArray(t.urls)?t.urls:[t.urls]).some(n=>Vs.includes(js(n)))}function Gn(t){if(typeof t!="string"||t.length===0||t.length>512)return!1;for(let e=0;e<t.length;e++){let n=t.charCodeAt(e);if(n<32||n===127)return!1}return!0}function Ka(t,e){if(!Array.isArray(t)||!e||!Gn(e.username)||!Gn(e.credential))return!1;let n=!1;for(let r of t)Ya(r)&&(r.username=e.username,r.credential=e.credential,n=!0);if(!n&&Array.isArray(e.urls)){let r=e.urls.filter(i=>Gn(i)&&Vs.includes(js(i)));r.length>0&&(t.push({urls:r,username:e.username,credential:e.credential}),n=!0)}return n}function Qa(t,e){let n=t&&Array.isArray(t.iceServers)?t.iceServers[0]:null;if(!n||!Gn(n.username)||!Gn(n.credential))return null;let r=Number(String(n.username).split(":")[0]);if(!Number.isFinite(r)||r<=e)return null;let i=Number(t.ttl);return{urls:Array.isArray(n.urls)?n.urls.slice(0,8):[],username:n.username,credential:n.credential,expiry:r,ttl:Number.isFinite(i)&&i>0?i:r-e}}var Ws=!1;function $s(t={}){if(Ws||typeof window>"u"||typeof fetch!="function")return;Ws=!0;let e=t.getList||(()=>window.SECUREBIT_ICE_SERVERS),n=0,r=0,i=null,c=0,d=m=>{i&&clearTimeout(i),i=setTimeout(h,m)};async function h(){i=null;try{let m=await fetch(qa,{method:"POST",cache:"no-store",credentials:"omit"});if(!m.ok)throw new Error(`HTTP ${m.status}`);let v=Qa(await m.json(),Date.now()/1e3);if(!v)throw new Error("unusable response");Ka(e(),v),n=v.expiry,r=v.ttl,c=0,d(Math.max(60,r/2)*1e3)}catch{let v=Hs[Math.min(c,Hs.length-1)];c++,d(v)}}document.addEventListener("visibilitychange",()=>{if(document.visibilityState!=="visible")return;let m=Date.now()/1e3;(!n||n-m<r/2)&&h()}),h()}var Tn=["en","de","fr","es","uk","ru","zh","ko","hi","ar","he","fa","ur"],or={en:{htmlLang:"en",nativeName:"English",abbr:"EN",dir:"ltr",path:"/"},de:{htmlLang:"de",nativeName:"Deutsch",abbr:"DE",dir:"ltr",path:"/de/"},fr:{htmlLang:"fr",nativeName:"Fran\xE7ais",abbr:"FR",dir:"ltr",path:"/fr/"},es:{htmlLang:"es",nativeName:"Espa\xF1ol",abbr:"ES",dir:"ltr",path:"/es/"},uk:{htmlLang:"uk",nativeName:"\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430",abbr:"UK",dir:"ltr",path:"/uk/"},ru:{htmlLang:"ru",nativeName:"\u0420\u0443\u0441\u0441\u043A\u0438\u0439",abbr:"RU",dir:"ltr",path:"/ru/"},zh:{htmlLine truncated function ja(t=globalThis.window){return t?.SECUREBIT_DEBUG===!0}function Us({targetWindow:t=globalThis.window,webrtcManagerRef:e,onClearData:n,clearConsole:r=()=>{typeof console.clear=="function"&&console.clear()}}){return ja(t)?(t.forceCleanup=()=>{n(),e.current&&e.current.disconnect()},t.clearLogs=r,t.webrtcManagerRef=e,()=>{delete t.forceCleanup,delete t.clearLogs,delete t.webrtcManagerRef}):()=>{}}var $a="securebit-net";var Wr="ice-device-key",jr="ice-settings";function $r(){return typeof indexedDB<"u"&&typeof crypto<"u"&&!!crypto.subtle}function qr(){return new Promise((t,e)=>{let n;try{n=indexedDB.open($a,1)}catch(r){e(r);return}n.onupgradeneeded=()=>{let r=n.result;r.objectStoreNames.contains("kv")||r.createObjectStore("kv")},n.onsuccess=()=>t(n.result),n.onerror=()=>e(n.error)})}function Vr(t,e){return new Promise((n,r)=>{let c=t.transaction("kv","readonly").objectStore("kv").get(e);c.onsuccess=()=>n(c.result),c.onerror=()=>r(c.error)})}function Gs(t,e,n){return new Promise((r,i)=>{let c=t.transaction("kv","readwrite");c.objectStore("kv").put(n,e),c.oncomplete=()=>r(),c.onerror=()=>i(c.error)})}function qa(t,e){return new Promise((n,r)=>{let i=t.transaction("kv","readwrite");i.objectStore("kv").delete(e),i.oncomplete=()=>n(),i.onerror=()=>r(i.error)})}async function Ya(t){let e=await Vr(t,Wr);if(e instanceof CryptoKey)return e;let n=await crypto.subtle.generateKey({name:"AES-GCM",length:256},!1,["encrypt","decrypt"]);return await Gs(t,Wr,n),n}async function Hs(t){if(!$r())throw new Error("Persistent storage is not available in this browser");let e=await qr(),n=await Ya(e),r=JSON.stringify({version:1,servers:Array.isArray(t?.servers)?t.servers:[],privacyMode:t?.privacyMode==="relay-only"?"relay-only":"standard"}),i=crypto.getRandomValues(new Uint8Array(12)),c=await crypto.subtle.encrypt({name:"AES-GCM",iv:i},n,new TextEncoder().encode(r));await Gs(e,jr,{iv:Array.from(i),data:Array.from(new Uint8Array(c))})}async function Ws(){if(!$r())return null;try{let t=await qr(),e=await Vr(t,jr);if(!e||!Array.isArray(e.iv)||!Array.isArray(e.data))return null;let n=await Vr(t,Wr);if(!(n instanceof CryptoKey))return null;let r=await crypto.subtle.decrypt({name:"AES-GCM",iv:new Uint8Array(e.iv)},n,new Uint8Array(e.data)),i=JSON.parse(new TextDecoder().decode(r));return{servers:Array.isArray(i.servers)?i.servers:[],privacyMode:i.privacyMode==="relay-only"?"relay-only":"standard"}}catch{return null}}async function Yr(){if($r())try{let t=await qr();await qa(t,jr)}catch{}}var Ka="/api/turn-credentials",$s=["turn.securebit.chat","144.172.96.126"];var Vs=[6e4,3e5,9e5];function qs(t){let e=/^turns?:([^:?\s]+)/i.exec(String(t||"").trim());return e?e[1].toLowerCase():null}function Qa(t){return!t||typeof t!="object"?!1:(Array.isArray(t.urls)?t.urls:[t.urls]).some(n=>$s.includes(qs(n)))}function Hn(t){if(typeof t!="string"||t.length===0||t.length>512)return!1;for(let e=0;e<t.length;e++){let n=t.charCodeAt(e);if(n<32||n===127)return!1}return!0}function Xa(t,e){if(!Array.isArray(t)||!e||!Hn(e.username)||!Hn(e.credential))return!1;let n=!1;for(let r of t)Qa(r)&&(r.username=e.username,r.credential=e.credential,n=!0);if(!n&&Array.isArray(e.urls)){let r=e.urls.filter(i=>Hn(i)&&$s.includes(qs(i)));r.length>0&&(t.push({urls:r,username:e.username,credential:e.credential}),n=!0)}return n}function Ja(t,e){let n=t&&Array.isArray(t.iceServers)?t.iceServers[0]:null;if(!n||!Hn(n.username)||!Hn(n.credential))return null;let r=Number(String(n.username).split(":")[0]);if(!Number.isFinite(r)||r<=e)return null;let i=Number(t.ttl);return{urls:Array.isArray(n.urls)?n.urls.slice(0,8):[],username:n.username,credential:n.credential,expiry:r,ttl:Number.isFinite(i)&&i>0?i:r-e}}var js=!1;function Ys(t={}){if(js||typeof window>"u"||typeof fetch!="function")return;js=!0;let e=t.getList||(()=>window.SECUREBIT_ICE_SERVERS),n=0,r=0,i=null,c=0,d=m=>{i&&clearTimeout(i),i=setTimeout(h,m)};async function h(){i=null;try{let m=await fetch(Ka,{method:"POST",cache:"no-store",credentials:"omit"});if(!m.ok)throw new Error(`HTTP ${m.status}`);let v=Ja(await m.json(),Date.now()/1e3);if(!v)throw new Error("unusable response");Xa(e(),v),n=v.expiry,r=v.ttl,c=0,d(Math.max(60,r/2)*1e3)}catch{let v=Vs[Math.min(c,Vs.length-1)];c++,d(v)}}document.addEventListener("visibilitychange",()=>{if(document.visibilityState!=="visible")return;let m=Date.now()/1e3;(!n||n-m<r/2)&&h()}),h()}var Nn=["en","de","fr","es","uk","ru","zh","ko","hi","ar","he","fa","ur"],or={en:{htmlLang:"en",nativeName:"English",abbr:"EN",dir:"ltr",path:"/"},de:{htmlLang:"de",nativeName:"Deutsch",abbr:"DE",dir:"ltr",path:"/de/"},fr:{htmlLang:"fr",nativeName:"Fran\xE7ais",abbr:"FR",dir:"ltr",path:"/fr/"},es:{htmlLang:"es",nativeName:"Espa\xF1ol",abbr:"ES",dir:"ltr",path:"/es/"},uk:{htmlLang:"uk",nativeName:"\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430",abbr:"UK",dir:"ltr",path:"/uk/"},ru:{htmlLang:"ru",nativeName:"\u0420\u0443\u0441\u0441\u043A\u0438\u0439",abbr:"RU",dir:"ltr",path:"/ru/"},zh:{htmlLine truncated
Please wait for real-time cryptographic verification to complete.`,"sec.verificationUnavailable":"Security verification not available","chat.placeholder":"Type an encrypted message\u2026","chat.send":"Send message","chat.sendFiles":"Send files","chat.hideFiles":"Hide files","chat.recordVoice":"Record voice message","chat.sendVoice":"Send voice message","chat.discard":"Discard","chat.codeBlock":"Send as a code block (expands the input)","chat.codeHint":"Code snippet \xB7 formatting preserved \xB7 \u2318\u21B5 to send","chat.viewOnce":"View once","chat.viewOnceTitle":"View once - vanishes after the peer reads it","chat.viewOncePrefix":"View once \xB7 ","chat.viewOnceTap":"View once \xB7 tap to reveal","chat.viewedOnce":"Viewed once","chat.disappearing":"Disappearing message - deletes on both sides","chat.disappearAfter":"Disappear after","chat.visibleFor":"Visible for","chat.timerPrefix":"Timer \xB7 ","chat.expired":"This message has expired","chat.deleteForEveryone":"Delete for everyone","chat.sending":"Sending","chat.delivered":"Delivered","chat.notSent":"Not sent","chat.notSentReason":"Not sent - the secure channel is not ready. Reconnect to continue.","chat.encrypted":"Encrypted","chat.decrypted":"Decrypted","chat.encryptedOnDevice":"Encrypted on your device","chat.uploading":"Uploading","chat.downloading":"Downloading","chat.transferring":"Transferring\u2026","chat.copied":"Copied!","chat.collapse":"Collapse","chat.closeMenu":"Close menu","chat.newChat":"New chat","chat.newGroup":"New group","chat.createGroup":"Create a group","chat.groupChats":"Group chats","chat.secureChat":"Secure chat","chat.nameThis":"Name this chat","chat.rename":"Rename chat (local only)","chat.renameHint":"Double-click to rename","chat.localLabel":"Local label \xB7 stored only on this device","chat.setStatus":"Set your status","chat.yourStatusPrefix":"Your status - ","chat.meshHint":"Up to 8 peers \xB7 P2P mesh","chat.offline":"Offline","chat.noNetwork":"No network \xB7 reconnecting","chat.startVoiceCall":"Start encrypted voice call","chat.startVideoCall":"Start encrypted video call","chat.verifyForCalls":"Verify the session to enable calls","chat.e2eeNote":"Every message is end-to-end encrypted on your device before it leaves.","chat.peersOnly":"Sent end-to-end to connected peers only - never stored on a server.","flow.invitationCreated":"Secure invitation created","flow.invitationCreatedBang":"Secure invitation created and encrypted!","flow.responseCreated":"Secure response created","flow.responseCreatedBang":"Secure response created!","flow.sendInvitation":"Send the invitation code to your interlocutor via a secure channel (voice call, SMS, etc.).","flow.sendResponse":"Send the response code to the initiator via a secure channel or let them scan the QR code below.","flow.sendEncryptedCode":"Send the encrypted code","flow.sendTheResponse":"Send the response","flow.pasteOrWrite":"Paste or write code\u2026","flow.processingInvitation":"Processing the secure invitation...","flow.processingResponse":"Processing the secure response...","flow.finalizing":"Finalizing the secure connection...","flow.channelEstablished":"Secure channel established","flow.channelReady":"Secure channel is ready","flow.restoring":"Restoring connection\u2026","flow.invitationCaptured":"Invitation captured.","err.needInvitation":"You need to insert the invitation code from your interlocutor.","err.needResponse":"You need to insert the response code from your interlocutor.","err.responseFormat":"Invalid response format - please check the code","err.responseNoKey":"Invalid response code - missing or corrupted cryptographic key. Please check the code and try again.","err.responseNoSignKey":"Invalid response code - missing signature verification key. Please check the code and try again.","err.responseOutdated":"Response data is outdated - please use a fresh invitation","err.setupError":"Connection setup error","err.notReady":"Connection not ready","err.securityBreach":"Security breach detected - connection rejected","err.securityValidation":"Security validation failed - possible attack detected","err.retiredQr":"This QR code uses a retired format that could not transfer the invitation. Ask your peer to generate a new one, or use copy/paste.","err.compressedQrNote":"Compressed QR may omit SDP for brevity. Use copy/paste if connection fails.","err.inviteNotConnected":"The invitation could not be sent. That chat is not connected right now.","err.inviteNotConnectedRetry":"The invitation could not be sent. That chat is not connected right now - reopen it and try again.","err.nobodyAccepted":"Nobody accepted the invitation. The group is unchanged.","err.noAudio":"No audio captured - check microphone permission and try again.","err.voiceNeedsConnection":"Voice message needs an active secure connection. Reconnect and try again.","err.voiceRestoring":"Restoring the connection - try sending the voice message again in a moment.","err.fileTooLarge":"File too large","sas.sa Line truncated Please wait for real-time cryptographic verification to complete.`,"sec.verificationUnavailable":"Security verification not available","chat.placeholder":"Type an encrypted message\u2026","chat.send":"Send message","chat.sendFiles":"Send files","chat.hideFiles":"Hide files","chat.recordVoice":"Record voice message","chat.sendVoice":"Send voice message","chat.discard":"Discard","chat.codeBlock":"Send as a code block (expands the input)","chat.codeHint":"Code snippet \xB7 formatting preserved \xB7 \u2318\u21B5 to send","chat.viewOnce":"View once","chat.viewOnceTitle":"View once - vanishes after the peer reads it","chat.viewOncePrefix":"View once \xB7 ","chat.viewOnceTap":"View once \xB7 tap to reveal","chat.viewedOnce":"Viewed once","chat.disappearing":"Disappearing message - deletes on both sides","chat.disappearAfter":"Disappear after","chat.visibleFor":"Visible for","chat.timerPrefix":"Timer \xB7 ","chat.expired":"This message has expired","chat.deleteForEveryone":"Delete for everyone","chat.sending":"Sending","chat.delivered":"Delivered","chat.notSent":"Not sent","chat.notSentReason":"Not sent - the secure channel is not ready. Reconnect to continue.","chat.encrypted":"Encrypted","chat.decrypted":"Decrypted","chat.encryptedOnDevice":"Encrypted on your device","chat.uploading":"Uploading","chat.downloading":"Downloading","chat.transferring":"Transferring\u2026","chat.copied":"Copied!","chat.collapse":"Collapse","chat.closeMenu":"Close menu","chat.newChat":"New chat","chat.newGroup":"New group","chat.createGroup":"Create a group","chat.groupChats":"Group chats","chat.secureChat":"Secure chat","chat.nameThis":"Name this chat","chat.rename":"Rename chat (local only)","chat.renameHint":"Double-click to rename","chat.localLabel":"Local label \xB7 stored only on this device","chat.setStatus":"Set your status","chat.yourStatusPrefix":"Your status - ","chat.meshHint":"Up to 8 peers \xB7 P2P mesh","chat.offline":"Offline","chat.noNetwork":"No network \xB7 reconnecting","chat.startVoiceCall":"Start encrypted voice call","chat.startVideoCall":"Start encrypted video call","chat.verifyForCalls":"Verify the session to enable calls","chat.e2eeNote":"Every message is end-to-end encrypted on your device before it leaves.","chat.peersOnly":"Sent end-to-end to connected peers only - never stored on a server.","flow.invitationCreated":"Secure invitation created","flow.invitationCreatedBang":"Secure invitation created and encrypted!","flow.responseCreated":"Secure response created","flow.responseCreatedBang":"Secure response created!","flow.sendInvitation":"Send the invitation code to your interlocutor via a secure channel (voice call, SMS, etc.).","flow.sendResponse":"Send the response code to the initiator via a secure channel or let them scan the QR code below.","flow.sendEncryptedCode":"Send the encrypted code","flow.sendTheResponse":"Send the response","flow.pasteOrWrite":"Paste or write code\u2026","flow.processingInvitation":"Processing the secure invitation...","flow.processingResponse":"Processing the secure response...","flow.finalizing":"Finalizing the secure connection...","flow.channelEstablished":"Secure channel established","flow.channelReady":"Secure channel is ready","flow.restoring":"Restoring connection\u2026","flow.invitationCaptured":"Invitation captured.","err.needInvitation":"You need to insert the invitation code from your interlocutor.","err.needResponse":"You need to insert the response code from your interlocutor.","err.responseFormat":"Invalid response format - please check the code","err.responseNoKey":"Invalid response code - missing or corrupted cryptographic key. Please check the code and try again.","err.responseNoSignKey":"Invalid response code - missing signature verification key. Please check the code and try again.","err.responseOutdated":"Response data is outdated - please use a fresh invitation","err.setupError":"Connection setup error","err.notReady":"Connection not ready","err.securityBreach":"Security breach detected - connection rejected","err.securityValidation":"Security validation failed - possible attack detected","err.retiredQr":"This QR code uses a retired format that could not transfer the invitation. Ask your peer to generate a new one, or use copy/paste.","err.compressedQrNote":"Compressed QR may omit SDP for brevity. Use copy/paste if connection fails.","err.inviteNotConnected":"The invitation could not be sent. That chat is not connected right now.","err.inviteNotConnectedRetry":"The invitation could not be sent. That chat is not connected right now - reopen it and try again.","err.nobodyAccepted":"Nobody accepted the invitation. The group is unchanged.","err.noAudio":"No audio captured - check microphone permission and try again.","err.voiceNeedsConnection":"Voice message needs an active secure connection. Reconnect and try again.","err.voiceRestoring":"Restoring the connection - try sending the voice message again in a moment.","err.fileTooLarge":"File too large","sas.sa Line truncated
`),n.style.transform="translate3d(0,"+(t?Math.round(t.offsetTop):0)+"px,0)"};i();let c=setInterval(i,200);return()=>{clearInterval(c),n.remove()}}var Xo=t=>()=>{if(t&&t.current){let e=()=>{t.current&&t.current.scrollTo({top:t.current.scrollHeight,behavior:wr()})};e(),setTimeout(e,50),setTimeout(e,150),setTimeout(e,300),requestAnimationFrame(()=>{setTimeout(e,100)})}},Ji=async t=>{let e=null;try{t&&window.EnhancedSecureCryptoUtils&&(e=await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(t))}catch{}if(!e){alert(`Security verification in progress\u2026 `),n.style.transform="translate3d(0,"+(t?Math.round(t.offsetTop):0)+"px,0)"};i();let c=setInterval(i,200);return()=>{clearInterval(c),n.remove()}}var Zo=t=>()=>{if(t&&t.current){let e=()=>{t.current&&t.current.scrollTo({top:t.current.scrollHeight,behavior:wr()})};e(),setTimeout(e,50),setTimeout(e,150),setTimeout(e,300),requestAnimationFrame(()=>{setTimeout(e,100)})}},ta=async t=>{let e=null;try{t&&window.EnhancedSecureCryptoUtils&&(e=await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(t))}catch{}if(!e){alert(`Security verification in progress\u2026
Please wait for real-time cryptographic verification to complete.`);return}let n="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",r=O=>String(O).replace(/[&<>"]/g,$=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[$]),i=e.color==="orange"?"var(--sb-orange-solid)":e.color==="yellow"?"var(--sb-yellow-solid)":e.color==="red"?"var(--sb-red-solid)":"var(--sb-green-solid)",c=e.color==="orange"?"var(--sb-orange)":e.color==="yellow"?"var(--sb-yellow)":e.color==="red"?"var(--sb-red)":"var(--sb-green)",d=e.color==="orange"?"var(--sb-orange-rgb)":e.color==="yellow"?"var(--sb-yellow-rgb)":e.color==="red"?"var(--sb-red-rgb)":"var(--sb-green-rgb)",h=Math.max(0,Math.min(100,Math.round(e.score||0))),m=2*Math.PI*56,v=`${(m*Math.min(1,h/100)).toFixed(1)} ${m.toFixed(1)}`,S=String(e.level||"SECURE").toUpperCase(),p=e.isRealData!==!1,k=e.verificationResults?Object.entries(e.verificationResults):[],y=Number.isFinite(e.passedChecks)?e.passedChecks:k.filter(([,O])=>O&&O.passed).length,I=Number.isFinite(e.totalChecks)?e.totalChecks:k.length,B=new Date(e.timestamp||Date.now()).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}),N=O=>{let $=a(`secTest.${O}`);return $===`secTest.${O}`?ae(O):$},ae=O=>{let $=String(O).replace(/^verify/i,"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").trim();return $=$.replace(/\b(ecdh|ecdsa|aes|gcm|hmac|pfs|sas|mitm|asn|dtls|hkdf|spki|oid|p384)\b/gi,Q=>Q.toUpperCase()),$.charAt(0).toUpperCase()+$.slice(1)},te='<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="var(--sb-green-solid)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4 10-11"/></svg>',pe='<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--sb-red-solid)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg>',ve=k.map(([O,$],Q)=>{let H=!!($&&$.passed),re=$&&$.details||(H?a("sec.testPassed"):a("sec.testFailed")),ie=a(`secDetail.${re}`),ue=ie===`secDetail.${re}`?re:ie,L=H?"var(--sb-surface)":"var(--sb-bg)",j=H?"rgba(var(--sb-green-rgb), 0.16)":"rgba(var(--sb-red-rgb), 0.18)",ne=H?"rgba(var(--sb-green-rgb), 0.12)":"rgba(var(--sb-red-rgb), 0.1)",Te=H?"rgba(var(--sb-green-rgb), 0.26)":"rgba(var(--sb-red-rgb), 0.24)",q=H?"var(--sb-text-1)":"var(--sb-text-4)";return`<div style="display:flex; align-items:flex-start; gap:13px; padding:16px 18px; border-radius:14px; background:${L}; border:1px solid ${j}; animation:svIn .3s cubic-bezier(.2,.7,.3,1) both; animation-delay:${(Q*.04).toFixed(2)}s;"> Please wait for real-time cryptographic verification to complete.`);return}let n="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",r=O=>String(O).replace(/[&<>"]/g,$=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[$]),i=e.color==="orange"?"var(--sb-orange-solid)":e.color==="yellow"?"var(--sb-yellow-solid)":e.color==="red"?"var(--sb-red-solid)":"var(--sb-green-solid)",c=e.color==="orange"?"var(--sb-orange)":e.color==="yellow"?"var(--sb-yellow)":e.color==="red"?"var(--sb-red)":"var(--sb-green)",d=e.color==="orange"?"var(--sb-orange-rgb)":e.color==="yellow"?"var(--sb-yellow-rgb)":e.color==="red"?"var(--sb-red-rgb)":"var(--sb-green-rgb)",h=Math.max(0,Math.min(100,Math.round(e.score||0))),m=2*Math.PI*56,v=`${(m*Math.min(1,h/100)).toFixed(1)} ${m.toFixed(1)}`,S=String(e.level||"SECURE").toUpperCase(),p=e.isRealData!==!1,k=e.verificationResults?Object.entries(e.verificationResults):[],y=Number.isFinite(e.passedChecks)?e.passedChecks:k.filter(([,O])=>O&&O.passed).length,I=Number.isFinite(e.totalChecks)?e.totalChecks:k.length,B=new Date(e.timestamp||Date.now()).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}),N=O=>{let $=a(`secTest.${O}`);return $===`secTest.${O}`?ae(O):$},ae=O=>{let $=String(O).replace(/^verify/i,"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").trim();return $=$.replace(/\b(ecdh|ecdsa|aes|gcm|hmac|pfs|sas|mitm|asn|dtls|hkdf|spki|oid|p384)\b/gi,Q=>Q.toUpperCase()),$.charAt(0).toUpperCase()+$.slice(1)},te='<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="var(--sb-green-solid)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4 10-11"/></svg>',pe='<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--sb-red-solid)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg>',ve=k.map(([O,$],Q)=>{let H=!!($&&$.passed),re=$&&$.details||(H?a("sec.testPassed"):a("sec.testFailed")),ie=a(`secDetail.${re}`),ue=ie===`secDetail.${re}`?re:ie,L=H?"var(--sb-surface)":"var(--sb-bg)",j=H?"rgba(var(--sb-green-rgb), 0.16)":"rgba(var(--sb-red-rgb), 0.18)",ne=H?"rgba(var(--sb-green-rgb), 0.12)":"rgba(var(--sb-red-rgb), 0.1)",Te=H?"rgba(var(--sb-green-rgb), 0.26)":"rgba(var(--sb-red-rgb), 0.24)",q=H?"var(--sb-text-1)":"var(--sb-text-4)";return`<div style="display:flex; align-items:flex-start; gap:13px; padding:16px 18px; border-radius:14px; background:${L}; border:1px solid ${j}; animation:svIn .3s cubic-bezier(.2,.7,.3,1) both; animation-delay:${(Q*.04).toFixed(2)}s;">
<span style="flex:none; width:34px; height:34px; border-radius:9px; display:grid; place-items:center; background:${ne}; border:1px solid ${Te};">${H?te:pe}</span> <span style="flex:none; width:34px; height:34px; border-radius:9px; display:grid; place-items:center; background:${ne}; border:1px solid ${Te};">${H?te:pe}</span>
<div style="flex:1; min-width:0;"> <div style="flex:1; min-width:0;">
@@ -47,5 +47,5 @@ Please wait for real-time cryptographic verification to complete.`);return}let n
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="${i}" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" style="flex:none;"><path d="M12 3l8 4v5c0 4.5-3.2 7.8-8 9-4.8-1.2-8-4.5-8-9V7l8-4z"/><path d="M9.2 12.2l2 2 3.6-3.8"/></svg> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="${i}" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" style="flex:none;"><path d="M12 3l8 4v5c0 4.5-3.2 7.8-8 9-4.8-1.2-8-4.5-8-9V7l8-4z"/><path d="M9.2 12.2l2 2 3.6-3.8"/></svg>
<span style="font-size:14px; font-weight:600; color:var(--sb-text-2);">${p?a("sec.realTimeNote"):a("sec.simulatedWarning")}</span> <span style="font-size:14px; font-weight:600; color:var(--sb-text-2);">${p?a("sec.realTimeNote"):a("sec.simulatedWarning")}</span>
</div> </div>
</div>`;let F=O=>{O.key==="Escape"&&V()},V=()=>{C.parentNode&&C.remove(),document.removeEventListener("keydown",F)};C.querySelector(".sv-close").addEventListener("click",V),C.addEventListener("click",O=>{O.target===C&&V()}),document.addEventListener("keydown",F);let P=C.querySelector(".sv-rerun");P.addEventListener("mouseenter",()=>{P.style.borderColor="rgba(var(--sb-orange-rgb), 0.45)",P.style.color="var(--sb-orange)"}),P.addEventListener("mouseleave",()=>{P.style.borderColor="rgba(var(--sb-ink), 0.1)",P.style.color="var(--sb-text-4)"}),P.addEventListener("click",()=>{V(),Ji(t)}),document.body.appendChild(C)},Jo=({status:t,onDisconnect:e,webrtcManager:n,title:r,isOffline:i,peerPresence:c,onRenameTitle:d})=>{let h="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",[m,v]=React.useState(!1),[S,p]=React.useState(null),[k,y]=React.useState(!1),[I,B]=React.useState("");React.useEffect(()=>{let q=!0,me=async()=>{try{if(!n)return;let Re=null;typeof n.getRealSecurityLevel=="function"?Re=await n.getRealSecurityLevel():typeof n.calculateAndReportSecurityLevel=="function"?Re=await n.calculateAndReportSecurityLevel():window.EnhancedSecureCryptoUtils&&(Re=await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(n)),q&&Re&&Re.isRealData!==!1&&p(Re)}catch{}};me();let Be=Re=>{q&&Re.detail&&Re.detail.securityData&&p(Re.detail.securityData)};document.addEventListener("real-security-calculated",Be);let rt=setInterval(me,15e3);return()=>{q=!1,clearInterval(rt),document.removeEventListener("real-security-calculated",Be)}},[n]);let N=t==="connected"||t==="verified",ae=t==="disconnected"||t==="peer_disconnected",te=N&&!i,pe=i||ae?"var(--sb-red-solid)":N?"var(--sb-green-solid)":"var(--sb-yellow-solid)",ve=i?a("chat.offline"):N?a("status.connected"):t==="peer_disconnected"?a("status.peerDisconnected"):t==="disconnected"?a("chat.disconnected"):a("status.connecting"),C=i||ae?"0 0 0 3px rgba(var(--sb-red-rgb), 0.16)":N?"0 0 0 3px rgba(var(--sb-green-rgb), 0.16)":"0 0 0 3px rgba(var(--sb-yellow-rgb), 0.16)",F=N&&!i?Xr[c]||"var(--sb-green-solid)":pe,V=N&&!i&&c&&Jr[c]||null,P=()=>{B(r||""),y(!0)},O=()=>{typeof d=="function"&&d(I),y(!1)},$=q=>{q.key==="Enter"?(q.preventDefault(),O()):q.key==="Escape"&&y(!1)},Q=S&&Number.isFinite(S.passedChecks)?S.passedChecks:null,H=S&&Number.isFinite(S.totalChecks)?S.totalChecks:null,re=q=>{if(!q)return q;let me=a(`secLevel.${q}`);return me===`secLevel.${q}`?q:me},ie=Q!=null&&H?Q+"/"+H:S?S.score+"%":"\u2014",ue=S?S.color==="green"?"var(--sb-green)":S.color==="orange"?"var(--sb-orange)":S.color==="yellow"?"var(--sb-yellow)":"var(--sb-red)":"var(--sb-green)",L=React.createElement("div",{key:"sec",title:a("sas.runVerification"),onClick:()=>Ji(n),className:"sb-secpill",style:{display:"flex",alignItems:"center",gap:"9px",padding:"7px 13px",borderRadius:"9px",border:"1px solid "+(m?"rgba(var(--sb-ink), 0.16)":"rgba(var(--sb-ink), 0.07)"),background:m?"rgba(var(--sb-ink), 0.05)":"rgba(var(--sb-ink), 0.02)",cursor:"pointer",fontFamily:"inherit",transition:"all .15s"}},[React.createElement("i",{key:"i",className:"fas fa-shield-halved",style:{color:ue,fontSize:"13px"}}),React.createElement("span",{key:"l",className:"sb-sec-label",style:{fontSize:"13px",fontWeight:600,color:"var(--sb-text-2)"}},S?re(S.level)||a("chatHdr.secure"):a("chatHdr.secure")),React.createElement("span",{key:"d",className:"sb-sec-div",style:{width:"1px",height:"13px",background:"rgba(var(--sb-ink), 0.12)"}}),React.createElement("span",{key:"s",className:"sb-sec-score",style:{fontFamily:h,fontSize:"11.5px",fontWeight:500,color:"var(--sb-text-7)"}},ie),React.createElement("button",{key:"c",type:"button",title:a("sec.panelTitle"),onClick:q=>{q.stopPropagation(),v(me=>!me)},style:{background:"none",border:"none",padding:0,margin:0,cursor:"pointer",display:"grid",placeItems:"center"}},React.createElement("i",{className:"fas fa-chevron-down",style:{color:"var(--sb-text-9)",fontSize:"11px",transform:m?"rotate(180deg)":"rotate(0deg)",transition:"transform .2s"}}))]),j=React.createElement("style",{key:"hdr-css",dangerouslySetInnerHTML:{__html:".sb-call-btn:not(.sb-call-off):hover{border-color:rgba(var(--sb-green-rgb), 0.45) !important;color:var(--sb-green) !important;background:rgba(var(--sb-green-rgb), 0.07) !important;}@media (max-width:768px){.sb-chat-header{padding-inline-start:58px !important;gap:8px !important;}.sb-chat-header .sb-secpill{display:none !important;}.sb-chat-header .sb-conn-text{display:none !important;}.sb-chat-header .sb-conn{padding:9px !important;}.sb-chat-header .sb-hdr-sub{display:none !important;}.sb-chat-header .sb-disconnect{width:40px !important;height:40px !important;padding:0 !important;gap:0 !important;justify-content:center !important;border-radius:9px !important;color:var(--sb-red) !important;border-color:rgba(var(--sb-red-rgb), 0.28) !important;}.sb-chat-header .sb-disconnect .sb-hide-sm{display:none !important;}}@media (max-width:600px){.sb-chips{flex-wrap:nowrap !important;jLine truncated </div>`;let F=O=>{O.key==="Escape"&&V()},V=()=>{C.parentNode&&C.remove(),document.removeEventListener("keydown",F)};C.querySelector(".sv-close").addEventListener("click",V),C.addEventListener("click",O=>{O.target===C&&V()}),document.addEventListener("keydown",F);let P=C.querySelector(".sv-rerun");P.addEventListener("mouseenter",()=>{P.style.borderColor="rgba(var(--sb-orange-rgb), 0.45)",P.style.color="var(--sb-orange)"}),P.addEventListener("mouseleave",()=>{P.style.borderColor="rgba(var(--sb-ink), 0.1)",P.style.color="var(--sb-text-4)"}),P.addEventListener("click",()=>{V(),ta(t)}),document.body.appendChild(C)},ec=({status:t,onDisconnect:e,webrtcManager:n,title:r,isOffline:i,peerPresence:c,onRenameTitle:d})=>{let h="'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace",[m,v]=React.useState(!1),[S,p]=React.useState(null),[k,y]=React.useState(!1),[I,B]=React.useState("");React.useEffect(()=>{let q=!0,ye=async()=>{try{if(!n)return;let Re=null;typeof n.getRealSecurityLevel=="function"?Re=await n.getRealSecurityLevel():typeof n.calculateAndReportSecurityLevel=="function"?Re=await n.calculateAndReportSecurityLevel():window.EnhancedSecureCryptoUtils&&(Re=await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(n)),q&&Re&&Re.isRealData!==!1&&p(Re)}catch{}};ye();let Be=Re=>{q&&Re.detail&&Re.detail.securityData&&p(Re.detail.securityData)};document.addEventListener("real-security-calculated",Be);let rt=setInterval(ye,15e3);return()=>{q=!1,clearInterval(rt),document.removeEventListener("real-security-calculated",Be)}},[n]);let N=t==="connected"||t==="verified",ae=t==="disconnected"||t==="peer_disconnected",te=N&&!i,pe=i||ae?"var(--sb-red-solid)":N?"var(--sb-green-solid)":"var(--sb-yellow-solid)",ve=i?a("chat.offline"):N?a("status.connected"):t==="peer_disconnected"?a("status.peerDisconnected"):t==="disconnected"?a("chat.disconnected"):a("status.connecting"),C=i||ae?"0 0 0 3px rgba(var(--sb-red-rgb), 0.16)":N?"0 0 0 3px rgba(var(--sb-green-rgb), 0.16)":"0 0 0 3px rgba(var(--sb-yellow-rgb), 0.16)",F=N&&!i?Jr[c]||"var(--sb-green-solid)":pe,V=N&&!i&&c&&Zr[c]||null,P=()=>{B(r||""),y(!0)},O=()=>{typeof d=="function"&&d(I),y(!1)},$=q=>{q.key==="Enter"?(q.preventDefault(),O()):q.key==="Escape"&&y(!1)},Q=S&&Number.isFinite(S.passedChecks)?S.passedChecks:null,H=S&&Number.isFinite(S.totalChecks)?S.totalChecks:null,re=q=>{if(!q)return q;let ye=a(`secLevel.${q}`);return ye===`secLevel.${q}`?q:ye},ie=Q!=null&&H?Q+"/"+H:S?S.score+"%":"\u2014",ue=S?S.color==="green"?"var(--sb-green)":S.color==="orange"?"var(--sb-orange)":S.color==="yellow"?"var(--sb-yellow)":"var(--sb-red)":"var(--sb-green)",L=React.createElement("div",{key:"sec",title:a("sas.runVerification"),onClick:()=>ta(n),className:"sb-secpill",style:{display:"flex",alignItems:"center",gap:"9px",padding:"7px 13px",borderRadius:"9px",border:"1px solid "+(m?"rgba(var(--sb-ink), 0.16)":"rgba(var(--sb-ink), 0.07)"),background:m?"rgba(var(--sb-ink), 0.05)":"rgba(var(--sb-ink), 0.02)",cursor:"pointer",fontFamily:"inherit",transition:"all .15s"}},[React.createElement("i",{key:"i",className:"fas fa-shield-halved",style:{color:ue,fontSize:"13px"}}),React.createElement("span",{key:"l",className:"sb-sec-label",style:{fontSize:"13px",fontWeight:600,color:"var(--sb-text-2)"}},S?re(S.level)||a("chatHdr.secure"):a("chatHdr.secure")),React.createElement("span",{key:"d",className:"sb-sec-div",style:{width:"1px",height:"13px",background:"rgba(var(--sb-ink), 0.12)"}}),React.createElement("span",{key:"s",className:"sb-sec-score",style:{fontFamily:h,fontSize:"11.5px",fontWeight:500,color:"var(--sb-text-7)"}},ie),React.createElement("button",{key:"c",type:"button",title:a("sec.panelTitle"),onClick:q=>{q.stopPropagation(),v(ye=>!ye)},style:{background:"none",border:"none",padding:0,margin:0,cursor:"pointer",display:"grid",placeItems:"center"}},React.createElement("i",{className:"fas fa-chevron-down",style:{color:"var(--sb-text-9)",fontSize:"11px",transform:m?"rotate(180deg)":"rotate(0deg)",transition:"transform .2s"}}))]),j=React.createElement("style",{key:"hdr-css",dangerouslySetInnerHTML:{__html:".sb-call-btn:not(.sb-call-off):hover{border-color:rgba(var(--sb-green-rgb), 0.45) !important;color:var(--sb-green) !important;background:rgba(var(--sb-green-rgb), 0.07) !important;}@media (max-width:768px){.sb-chat-header{padding-inline-start:58px !important;gap:8px !important;}.sb-chat-header .sb-secpill{display:none !important;}.sb-chat-header .sb-conn-text{display:none !important;}.sb-chat-header .sb-conn{padding:9px !important;}.sb-chat-header .sb-hdr-sub{display:none !important;}.sb-chat-header .sb-disconnect{width:40px !important;height:40px !important;padding:0 !important;gap:0 !important;justify-content:center !important;border-radius:9px !important;color:var(--sb-red) !important;border-color:rgba(var(--sb-red-rgb), 0.28) !important;}.sb-chat-header .sb-disconnect .sb-hide-sm{display:none !important;}}@media (max-width:600px){.sb-chips{flex-wrap:nowrap !important;jLine truncated
//# sourceMappingURL=app.js.map //# sourceMappingURL=app.js.map
+3 -3
View File
@@ -1,7 +1,7 @@
{ {
"version": 3, "version": 3,
"sources": ["../src/utils/debugWindowHooks.js", "../src/network/iceSettingsStore.js", "../src/network/turnCredentials.js", "../src/i18n/generated.js", "../src/i18n/dict/en.js", "../src/i18n/index.js", "../src/state/sessionsStore.js", "../src/group/groupCrypto.js", "../src/state/groupsStore.js", "../src/group/GroupSession.js", "../src/group/groupSender.js", "../src/ui/motion.js", "../src/components/ui/LanguageSuggestion.jsx", "../src/components/ui/GroupChat.jsx", "../src/group/groupCallMedia.js", "../src/components/ui/callLayout.js", "../src/components/ui/GroupCallUI.jsx", "../src/app.jsx"], "sources": ["../src/utils/debugWindowHooks.js", "../src/network/iceSettingsStore.js", "../src/network/turnCredentials.js", "../src/i18n/generated.js", "../src/i18n/dict/en.js", "../src/i18n/index.js", "../src/state/sessionsStore.js", "../src/group/groupCrypto.js", "../src/state/groupsStore.js", "../src/group/GroupSession.js", "../src/group/groupSender.js", "../src/ui/motion.js", "../src/components/ui/LanguageSuggestion.jsx", "../src/components/ui/GroupChat.jsx", "../src/group/groupCallMedia.js", "../src/components/ui/callLayout.js", "../src/components/ui/GroupCallUI.jsx", "../src/app.jsx"],
"sourcesContent": ["function isSecureBitDebugEnabled(targetWindow = globalThis.window) {\n return targetWindow?.SECUREBIT_DEBUG === true;\n}\n\nfunction installDebugWindowHooks({\n targetWindow = globalThis.window,\n webrtcManagerRef,\n onClearData,\n clearConsole = () => {\n if (typeof console.clear === 'function') {\n console.clear();\n }\n }\n}) {\n if (!isSecureBitDebugEnabled(targetWindow)) {\n return () => {};\n }\n\n targetWindow.forceCleanup = () => {\n onClearData();\n if (webrtcManagerRef.current) {\n webrtcManagerRef.current.disconnect();\n }\n };\n targetWindow.clearLogs = clearConsole;\n targetWindow.webrtcManagerRef = webrtcManagerRef;\n\n return () => {\n delete targetWindow.forceCleanup;\n delete targetWindow.clearLogs;\n delete targetWindow.webrtcManagerRef;\n };\n}\n\nexport { installDebugWindowHooks, isSecureBitDebugEnabled };\n", "// Persistent, at-rest-encrypted storage for the user's custom ICE (STUN/TURN)\n// configuration. Persistence is OPTIONAL: the UI only calls saveIceSettings when\n// the user explicitly opts in (\"Save on this device\"). Session-only use never\n// touches this store \u2014 the settings live in React state and vanish on reload.\n//\n// At-rest protection model:\n// - A non-extractable AES-GCM device key is generated once and kept in\n// IndexedDB. It can never be exported back into JS, so a copy of the\n// on-disk database is useless without executing code in this exact origin.\n// - The settings blob (which may contain TURN credentials) is encrypted with\n// that key before being written.\n// This protects against disk/profile inspection. It does NOT protect against a\n// live code-execution compromise of the page (consistent with the project's\n// stated threat model \u2014 see SECURITY.md). Credentials are never persisted in\n// plaintext, and the user can delete them at any time via clearIceSettings().\n\nconst DB_NAME = 'securebit-net';\nconst DB_VERSION = 1;\nconst STORE = 'kv';\nconst KEY_RECORD = 'ice-device-key';\nconst SETTINGS_RECORD = 'ice-settings';\nconst SETTINGS_VERSION = 1;\n\nfunction isSupported() {\n return typeof indexedDB !== 'undefined' &&\n typeof crypto !== 'undefined' &&\n !!crypto.subtle;\n}\n\nfunction openDb() {\n return new Promise((resolve, reject) => {\n let request;\n try {\n request = indexedDB.open(DB_NAME, DB_VERSION);\n } catch (error) {\n reject(error);\n return;\n }\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(STORE)) {\n db.createObjectStore(STORE);\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nfunction idbGet(db, key) {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE, 'readonly');\n const req = tx.objectStore(STORE).get(key);\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n}\n\nfunction idbPut(db, key, value) {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE, 'readwrite');\n tx.objectStore(STORE).put(value, key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nfunction idbDelete(db, key) {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE, 'readwrite');\n tx.objectStore(STORE).delete(key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nasync function getOrCreateDeviceKey(db) {\n const existing = await idbGet(db, KEY_RECORD);\n if (existing instanceof CryptoKey) {\n return existing;\n }\n const key = await crypto.subtle.generateKey(\n { name: 'AES-GCM', length: 256 },\n false, // non-extractable\n ['encrypt', 'decrypt']\n );\n await idbPut(db, KEY_RECORD, key);\n return key;\n}\n\n/**\n * Persist custom ICE settings, encrypted at rest.\n * @param {{ servers: Array, privacyMode: string }} settings\n */\nexport async function saveIceSettings(settings) {\n if (!isSupported()) throw new Error('Persistent storage is not available in this browser');\n\n const db = await openDb();\n const key = await getOrCreateDeviceKey(db);\n\n const payload = JSON.stringify({\n version: SETTINGS_VERSION,\n servers: Array.isArray(settings?.servers) ? settings.servers : [],\n privacyMode: settings?.privacyMode === 'relay-only' ? 'relay-only' : 'standard'\n });\n\n const iv = crypto.getRandomValues(new Uint8Array(12));\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv },\n keLine truncated "sourcesContent": ["function isSecureBitDebugEnabled(targetWindow = globalThis.window) {\n return targetWindow?.SECUREBIT_DEBUG === true;\n}\n\nfunction installDebugWindowHooks({\n targetWindow = globalThis.window,\n webrtcManagerRef,\n onClearData,\n clearConsole = () => {\n if (typeof console.clear === 'function') {\n console.clear();\n }\n }\n}) {\n if (!isSecureBitDebugEnabled(targetWindow)) {\n return () => {};\n }\n\n targetWindow.forceCleanup = () => {\n onClearData();\n if (webrtcManagerRef.current) {\n webrtcManagerRef.current.disconnect();\n }\n };\n targetWindow.clearLogs = clearConsole;\n targetWindow.webrtcManagerRef = webrtcManagerRef;\n\n return () => {\n delete targetWindow.forceCleanup;\n delete targetWindow.clearLogs;\n delete targetWindow.webrtcManagerRef;\n };\n}\n\nexport { installDebugWindowHooks, isSecureBitDebugEnabled };\n", "// Persistent, at-rest-encrypted storage for the user's custom ICE (STUN/TURN)\n// configuration. Persistence is OPTIONAL: the UI only calls saveIceSettings when\n// the user explicitly opts in (\"Save on this device\"). Session-only use never\n// touches this store \u2014 the settings live in React state and vanish on reload.\n//\n// At-rest protection model:\n// - A non-extractable AES-GCM device key is generated once and kept in\n// IndexedDB. It can never be exported back into JS, so a copy of the\n// on-disk database is useless without executing code in this exact origin.\n// - The settings blob (which may contain TURN credentials) is encrypted with\n// that key before being written.\n// This protects against disk/profile inspection. It does NOT protect against a\n// live code-execution compromise of the page (consistent with the project's\n// stated threat model \u2014 see SECURITY.md). Credentials are never persisted in\n// plaintext, and the user can delete them at any time via clearIceSettings().\n\nconst DB_NAME = 'securebit-net';\nconst DB_VERSION = 1;\nconst STORE = 'kv';\nconst KEY_RECORD = 'ice-device-key';\nconst SETTINGS_RECORD = 'ice-settings';\nconst SETTINGS_VERSION = 1;\n\nfunction isSupported() {\n return typeof indexedDB !== 'undefined' &&\n typeof crypto !== 'undefined' &&\n !!crypto.subtle;\n}\n\nfunction openDb() {\n return new Promise((resolve, reject) => {\n let request;\n try {\n request = indexedDB.open(DB_NAME, DB_VERSION);\n } catch (error) {\n reject(error);\n return;\n }\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(STORE)) {\n db.createObjectStore(STORE);\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nfunction idbGet(db, key) {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE, 'readonly');\n const req = tx.objectStore(STORE).get(key);\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n}\n\nfunction idbPut(db, key, value) {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE, 'readwrite');\n tx.objectStore(STORE).put(value, key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nfunction idbDelete(db, key) {\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE, 'readwrite');\n tx.objectStore(STORE).delete(key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n}\n\nasync function getOrCreateDeviceKey(db) {\n const existing = await idbGet(db, KEY_RECORD);\n if (existing instanceof CryptoKey) {\n return existing;\n }\n const key = await crypto.subtle.generateKey(\n { name: 'AES-GCM', length: 256 },\n false, // non-extractable\n ['encrypt', 'decrypt']\n );\n await idbPut(db, KEY_RECORD, key);\n return key;\n}\n\n/**\n * Persist custom ICE settings, encrypted at rest.\n * @param {{ servers: Array, privacyMode: string }} settings\n */\nexport async function saveIceSettings(settings) {\n if (!isSupported()) throw new Error('Persistent storage is not available in this browser');\n\n const db = await openDb();\n const key = await getOrCreateDeviceKey(db);\n\n const payload = JSON.stringify({\n version: SETTINGS_VERSION,\n servers: Array.isArray(settings?.servers) ? settings.servers : [],\n privacyMode: settings?.privacyMode === 'relay-only' ? 'relay-only' : 'standard'\n });\n\n const iv = crypto.getRandomValues(new Uint8Array(12));\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv },\n keLine truncated
"mappings": "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 truncated "mappings": "AAAA,SAASA,GAAwBC,EAAe,WAAW,OAAQ,CAC/D,OAAOA,GAAc,kBAAoB,EAC7C,CAEA,SAASC,GAAwB,CAC7B,aAAAD,EAAe,WAAW,OAC1B,iBAAAE,EACA,YAAAC,EACA,aAAAC,EAAe,IAAM,CACb,OAAO,QAAQ,OAAU,YACzB,QAAQ,MAAM,CAEtB,CACJ,EAAG,CACC,OAAKL,GAAwBC,CAAY,GAIzCA,EAAa,aAAe,IAAM,CAC9BG,EAAY,EACRD,EAAiB,SACjBA,EAAiB,QAAQ,WAAW,CAE5C,EACAF,EAAa,UAAYI,EACzBJ,EAAa,iBAAmBE,EAEzB,IAAM,CACT,OAAOF,EAAa,aACpB,OAAOA,EAAa,UACpB,OAAOA,EAAa,gBACxB,GAhBW,IAAM,CAAC,CAiBtB,CChBA,IAAMK,GAAU,gBAGhB,IAAMC,GAAa,iBACbC,GAAkB,eAGxB,SAASC,IAAc,CACnB,OAAO,OAAO,UAAc,KACxB,OAAO,OAAW,KAClB,CAAC,CAAC,OAAO,MACjB,CAEA,SAASC,IAAS,CACd,OAAO,IAAI,QAAQ,CAACC,EAASC,IAAW,CACpC,IAAIC,EACJ,GAAI,CACAA,EAAU,UAAU,KAAKC,GAAS,CAAU,CAChD,OAASC,EAAO,CACZH,EAAOG,CAAK,EACZ,MACJ,CACAF,EAAQ,gBAAkB,IAAM,CAC5B,IAAMG,EAAKH,EAAQ,OACdG,EAAG,iBAAiB,SAAS,IAAK,GACnCA,EAAG,kBAAkB,IAAK,CAElC,EACAH,EAAQ,UAAY,IAAMF,EAAQE,EAAQ,MAAM,EAChDA,EAAQ,QAAU,IAAMD,EAAOC,EAAQ,KAAK,CAChD,CAAC,CACL,CAEA,SAASI,GAAOD,EAAIE,EAAK,CACrB,OAAO,IAAI,QAAQ,CAACP,EAASC,IAAW,CAEpC,IAAMO,EADKH,EAAG,YAAY,KAAO,UAAU,EAC5B,YAAY,IAAK,EAAE,IAAIE,CAAG,EACzCC,EAAI,UAAY,IAAMR,EAAQQ,EAAI,MAAM,EACxCA,EAAI,QAAU,IAAMP,EAAOO,EAAI,KAAK,CACxC,CAAC,CACL,CAEA,SAASC,GAAOJ,EAAIE,EAAKG,EAAO,CAC5B,OAAO,IAAI,QAAQ,CAACV,EAASC,IAAW,CACpC,IAAMU,EAAKN,EAAG,YAAY,KAAO,WAAW,EAC5CM,EAAG,YAAY,IAAK,EAAE,IAAID,EAAOH,CAAG,EACpCI,EAAG,WAAa,IAAMX,EAAQ,EAC9BW,EAAG,QAAU,IAAMV,EAAOU,EAAG,KAAK,CACtC,CAAC,CACL,CAEA,SAASC,GAAUP,EAAIE,EAAK,CACxB,OAAO,IAAI,QAAQ,CAACP,EAASC,IAAW,CACpC,IAAMU,EAAKN,EAAG,YAAY,KAAO,WAAW,EAC5CM,EAAG,YAAY,IAAK,EAAE,OAAOJ,CAAG,EAChCI,EAAG,WAAa,IAAMX,EAAQ,EAC9BW,EAAG,QAAU,IAAMV,EAAOU,EAAG,KAAK,CACtC,CAAC,CACL,CAEA,eAAeE,GAAqBR,EAAI,CACpC,IAAMS,EAAW,MAAMR,GAAOD,EAAIU,EAAU,EAC5C,GAAID,aAAoB,UACpB,OAAOA,EAEX,IAAMP,EAAM,MAAM,OAAO,OAAO,YAC5B,CAAE,KAAM,UAAW,OAAQ,GAAI,EAC/B,GACA,CAAC,UAAW,SAAS,CACzB,EACA,aAAME,GAAOJ,EAAIU,GAAYR,CAAG,EACzBA,CACX,CAMA,eAAsBS,GAAgBC,EAAU,CAC5C,GAAI,CAACnB,GAAY,EAAG,MAAM,IAAI,MAAM,qDAAqD,EAEzF,IAAMO,EAAK,MAAMN,GAAO,EAClBQ,EAAM,MAAMM,GAAqBR,CAAE,EAEnCa,EAAU,KAAK,UAAU,CAC3B,QAAS,EACT,QAAS,MAAM,QAAQD,GAAU,OAAO,EAAIA,EAAS,QAAU,CAAC,EAChE,YAAaA,GAAU,cAAgB,aAAe,aAAe,UACzE,CAAC,EAEKE,EAAK,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC,EAC9CC,EAAa,MAAM,OAAO,OAAO,QACnC,CAAE,KAAM,UAAW,GAAAD,CAAG,EACtBZ,EACA,IAAI,YAAY,EAAE,OAAOW,CAAO,CACpC,EAEA,MAAMT,GAAOJ,EAAIgB,GAAiB,CAC9B,GAAI,MAAM,KAAKF,CAAE,EACjB,KAAM,MAAM,KAAK,IAAI,WAAWC,CAAU,CAAC,CAC/C,CAAC,CACL,CAOA,eAAsBE,IAAkB,CACpC,GAAI,CAACxB,GAAY,EAAG,OAAO,KAE3B,GAAI,CACA,IAAMO,EAAK,MAAMN,GAAO,EAClBwB,EAAS,MAAMjB,GAAOD,EAAIgB,EAAe,EAC/C,GAAI,CAACE,GAAU,CAAC,MAAM,QAAQA,EAAO,EAAE,GAAK,CAAC,MAAM,QAAQA,EAAO,IAAI,EAClE,OAAO,KAEX,IAAMhB,EAAM,MAAMD,GAAOD,EAAIU,EAAU,EACvC,GAAI,EAAER,aAAe,WAAY,OAAO,KAExC,IAAMiB,EAAY,MAAM,OAAO,OAAO,QAClC,CAAE,KAAM,UAAW,GAAI,IAAI,WAAWD,EAAO,EAAE,CAAE,EACjDhB,EACA,IAAI,WAAWgB,EAAO,IAAI,CAC9B,EACME,EAAS,KAAK,MAAM,IAAI,YAAY,EAAE,OAAOD,CAAS,CAAC,EAC7D,MAAO,CACH,QAAS,MAAM,QAAQC,EAAO,OAAO,EAAIA,EAAO,QAAU,CAAC,EAC3D,YAAaA,EAAO,cAAgB,aAAe,aAAe,UACtE,CACJ,MAAQ,CAEJ,OAAO,IACX,CACJ,CAGA,eAAsBC,IAAmB,CACrC,GAAK5B,GAAY,EACjB,GAAI,CACA,IAAMO,EAAK,MAAMN,GAAO,EACxB,MAAMa,GAAUP,EAAIgB,EAAe,CACvC,MAAQ,CAER,CACJ,CC9IA,IAAMM,GAAW,wBACXC,GAAkB,CAAC,sBAAuB,gBAAgB,EAGhE,IAAMC,GAAkB,CAAC,IAAQ,IAAY,GAAW,EAExD,SAASC,GAAUC,EAAK,CACpB,IAAMC,EAAI,sBAAsB,KAAK,OAAOD,GAAO,EAAE,EAAE,KAAK,CAAC,EAC7D,OAAOC,EAAIA,EAAE,CAAC,EAAE,YAAY,EAAI,IACpC,CAGO,SAASC,GAAgBC,EAAO,CACnC,MAAI,CAACA,GAAS,OAAOA,GAAU,SAAiB,IACnC,MAAM,QAAQA,EAAM,IAAI,EAAIA,EAAM,KAAO,CAACA,EAAM,IAAI,GACrD,KAAMC,GAAMC,GAAgB,SAASN,GAAUK,CAAC,CAAC,CAAC,CAClE,CAEA,SAASE,GAAaC,EAAO,CACzB,GAAI,OAAOA,GAAU,UAAYA,EAAM,SAAW,GAAKA,EAAM,OAAS,IAAW,MAAO,GACxF,QAASC,EAAI,EAAGA,EAAID,EAAM,OAAQC,IAAK,CACnC,IAAMC,EAAIF,EAAM,WAAWC,CAAC,EAC5B,GAAIC,EAAI,IAAQA,IAAM,IAAM,MAAO,EACvC,CACA,MAAO,EACX,CAOO,SAASC,GAAqBC,EAAMC,EAAM,CAC7C,GAAI,CAAC,MAAM,QAAQD,CAAI,GAAK,CAACC,GAAQ,CAACN,GAAaM,EAAK,QAAQ,GAAK,CAACN,GAAaM,EAAK,UAAU,EAC9F,MAAO,GAEX,IAAIC,EAAU,GACd,QAAWV,KAASQ,EACXT,GAAgBC,CAAK,IAC1BA,EAAM,SAAWS,EAAK,SACtBT,EAAM,WAAaS,EAAK,WACxBC,EAAU,IAEd,GAAI,CAACA,GAAW,MAAM,QAAQD,EAAK,IAAI,EAAG,CAGtC,IAAME,EAAOF,EAAK,KAAK,OAAQR,GAAME,GAAaF,CAAC,GAAKC,GAAgB,SAASN,GAAUK,CAAC,CAAC,CAAC,EAC1FU,EAAK,OAAS,IACdH,EAAK,KAAK,CAAE,KAAAG,EAAM,SAAUF,EAAK,SAAU,WAAYA,EAAK,UAAW,CAAC,EACxEC,EAAU,GAElB,CACA,OAAOA,CACX,CAGO,SAASE,GAAwBC,EAAMC,EAAY,CACtD,IAAMC,EAASF,GAAQ,MAAM,QAAQA,EAAK,UAAU,EAAIA,EAAK,WAAW,CAAC,EAAI,KAC7E,GAAI,CAACE,GAAU,CAACZ,GAAaY,EAAO,QAAQ,GAAK,CAACZ,GAAaY,EAAO,UAAU,EAAG,OAAO,KAE1F,IAAMC,EAAS,OAAO,OAAOD,EAAO,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC,EAC3D,GAAI,CAAC,OAAO,SAASC,CAAM,GAAKA,GAAUF,EAAY,OAAO,KAC7D,IAAMG,EAAM,OAAOJ,EAAK,GAAG,EAC3B,MAAO,CACH,KAAM,MAAM,QAAQE,EAAO,IAAI,EAAIA,EAAO,KAAK,MAAM,EAAG,CAAC,EAAI,CAAC,EAC9D,SAAUA,EAAO,SACjB,WAAYA,EAAO,WACnB,OAAAC,EACA,IAAK,OAAO,SAASC,CAAG,GAAKA,EAAM,EAAIA,EAAMD,EAASF,CAC1D,CACJ,CAEA,IAAII,GAAU,GAOP,SAASC,GAA2BC,EAAO,CAAC,EAAG,CAClD,GAAIF,IAAW,OAAO,OAAW,KAAe,OAAO,OAAU,WAAY,OAC7EA,GAAU,GAEV,IAAMG,EAAUD,EAAK,UAAY,IAAM,OAAO,uBAC1CJ,EAAS,EACTC,EAAM,EACNK,EAAQ,KACRC,EAAW,EAETC,EAAYC,GAAO,CACjBH,GAAO,aAAaA,CAAK,EAC7BA,EAAQ,WAAWI,EAASD,CALine truncated
"names": ["isSecureBitDebugEnabled", "targetWindow", "installDebugWindowHooks", "webrtcManagerRef", "onClearData", "clearConsole", "DB_NAME", "KEY_RECORD", "SETTINGS_RECORD", "isSupported", "openDb", "resolve", "reject", "request", "DB_NAME", "error", "db", "idbGet", "key", "req", "idbPut", "value", "tx", "idbDelete", "getOrCreateDeviceKey", "existing", "KEY_RECORD", "saveIceSettings", "settings", "payload", "iv", "ciphertext", "SETTINGS_RECORD", "loadIceSettings", "record", "plaintext", "parsed", "clearIceSettings", "ENDPOINT", "OWN_RELAY_HOSTS", "RETRY_DELAYS_MS", "relayHost", "url", "m", "isOwnRelayEntry", "entry", "u", "OWN_RELAY_HOSTS", "isCleanField", "value", "i", "c", "applyTurnCredentials", "list", "cred", "updated", "urls", "parseCredentialResponse", "body", "nowSeconds", "server", "expiry", "ttl", "started", "startTurnCredentialRefresh", "opts", "getList", "timer", "failures", "schedule", "ms", "refresh", "res", "ENDPOINT", "delay", "now", "SUPPORTED_LOCALES", "LOCALE_META", "CROSS_LOCALE_STRINGS", "DICTIONARY", "registry", "REGISTRY", "dictionary", "code", "STORAGE_KEY", "localeFromPathname", "pathname", "segment", "SUPPORTED_LOCALES", "localeFromLanguages", "languages", "tag", "lower", "exact", "base", "partial", "detectLocale", "stored", "fromPath", "isLocaleRoot", "localeHref", "current", "tail", "rememberLocale", "storedLocale", "resolvedLocale", "currentLocale", "w", "localeDir", "LOCALE_META", "isRTL", "direction", "SUGGEST_KEY", "localeSuggestionDismissed", "dismissLocaleSuggestion", "suggestedLocale", "pathname", "languages", "stored", "shown", "localeFromPathname", "preferred", "SUPPORTED_LOCALES", "localeFromLanguages", "t", "key", "vars", "locale", "currentLocale", "template", "dictionary", "CROSS_LOCALE_STRINGS", "match", "name", "SESSION_ACTIONS", "PRESENCE_DOT", "PRESENCE_WORD", "t", "MY_STATUS_OPTIONS", "shortLabelFromId", "id", "hex", "monoInitials", "label", "words", "a", "b", "statusSub", "status", "t", "emptySetup", "createSessionEntry", "opts", "id", "shortLabelFromId", "createInitialState", "patchSession", "state", "patch", "session", "sessionsReducer", "action", "A", "SESSION_ACTIONS", "entry", "sessions", "order", "x", "activeSessionId", "removedIdx", "next", "changed", "messages", "m", "byId", "byMid", "byFileId", "label", "decorateSession", "lastMessage", "s", "isUp", "isPending", "dot", "headerSub", "PRESENCE_DOT", "PRESENCE_WORD", "preview", "monoInitials", "decorateSessions", "GROUP_LIMITS", "CALL_ACTIONS", "MESH_KINDS", "MEMBER_OPS", "ENC", "GroupCryptoError", "message", "code", "fail", "msg", "toHex", "bytes", "view", "out", "i", "fromHex", "hex", "toB64", "binary", "fromB64", "b64", "max", "randomBytes", "n", "newGroupId", "lp", "label", "parts", "chunks", "total", "part", "header", "o", "c", "u32", "b", "equalBytes", "a", "diff", "assertGroupId", "groupId", "assertFingerprint", "fp", "assertEpoch", "epoch", "assertName", "name", "value", "canonicalFingerprints", "fps", "seen", "generateGroupIdentity", "subtle", "keyPair", "spki", "fingerprint", "fingerprintSpki", "importMemberIdentity", "publicKey", "buildCommitment", "nonce", "payload", "verifyCommitment", "commitment", "fields", "expected", "computeGroupSas", "contributions", "digits", "ordered", "ikm", "salt", "key", "bits", "dv", "GroupSasCeremony", "selfFingerprint", "memberFingerprints", "existing", "memberOpPayload", "op", "memberFps", "signMemberOp", "privateKey", "sig", "verifyMemberOp", "signature", "hashBody", "body", "groupMessagePayload", "seq", "senderFp", "bodyHash", "signGroupMessage", "verifyGroupMessage", "meshDescriptorPayload", "kind", "fromFp", "toFp", "descriptor", "signMeshDescriptor", "verifyMeshDescriptor", "linkProbePayload", "linkFp", "signLinkProbe", "verifyLinkProbe", "groupCallPayload", "callId", "action", "withVideo", "assertCallId", "newCallId", "signGroupCall", "verifyGroupCall", "GROUP_ACTIONS", "GROUP_PHASE", "MEMBER_STATE", "GROUP_PHASE_WORD", "t", "groupInitials", "name", "words", "a", "b", "createGroupEntry", "opts", "byFingerprint", "NAME_ENCODER", "clampNameBytes", "value", "out", "GROUP_LIMITS", "createInitialGroupState", "patchGroup", "state", "id", "patch", "group", "groupsReducer", "action", "A", "entry", "groups", "order", "x", "activeGroupId", "removedIdx", "changed", "members", "m", "next", "k", "messages", "linkedCount", "groupSub", "total", "linked", "groupDot", "decorateGroup", "lastMessage", "sub", "decorateGroups", "g", "GROUP_FRAMES", "GROUP_ENVELOPE", "GROUP_FRAME_TYPES", "isGroupFrame", "parsed", "groupFrameType", "encodeEnvelope", "frame", "json", "encoded", "toB64", "GROUP_LIMITS", "GroupSessionError", "decodeEnvelope", "envelope", "raw", "bytes", "fromB64", "TIMEOUTS", "MESH_MAX_CONCURRENT_DIALS", "MESH_MAX_ATTEMPTS", "message", "code", "GroupSession", "groupId", "name", "isAdmin", "subtle", "send", "emit", "mesh", "log", "assertGroupId", "assertName", "GROUP_PHASE", "newGroupId", "generateGroupIdentity", "MEMBER_STATE", "t", "sessionId", "fn", "ms", "phLine truncated "names": ["isSecureBitDebugEnabled", "targetWindow", "installDebugWindowHooks", "webrtcManagerRef", "onClearData", "clearConsole", "DB_NAME", "KEY_RECORD", "SETTINGS_RECORD", "isSupported", "openDb", "resolve", "reject", "request", "DB_NAME", "error", "db", "idbGet", "key", "req", "idbPut", "value", "tx", "idbDelete", "getOrCreateDeviceKey", "existing", "KEY_RECORD", "saveIceSettings", "settings", "payload", "iv", "ciphertext", "SETTINGS_RECORD", "loadIceSettings", "record", "plaintext", "parsed", "clearIceSettings", "ENDPOINT", "OWN_RELAY_HOSTS", "RETRY_DELAYS_MS", "relayHost", "url", "m", "isOwnRelayEntry", "entry", "u", "OWN_RELAY_HOSTS", "isCleanField", "value", "i", "c", "applyTurnCredentials", "list", "cred", "updated", "urls", "parseCredentialResponse", "body", "nowSeconds", "server", "expiry", "ttl", "started", "startTurnCredentialRefresh", "opts", "getList", "timer", "failures", "schedule", "ms", "refresh", "res", "ENDPOINT", "delay", "now", "SUPPORTED_LOCALES", "LOCALE_META", "CROSS_LOCALE_STRINGS", "DICTIONARY", "registry", "REGISTRY", "dictionary", "code", "STORAGE_KEY", "localeFromPathname", "pathname", "segment", "SUPPORTED_LOCALES", "localeFromLanguages", "languages", "tag", "lower", "exact", "base", "partial", "detectLocale", "stored", "fromPath", "isLocaleRoot", "localeHref", "current", "tail", "rememberLocale", "storedLocale", "resolvedLocale", "currentLocale", "w", "localeDir", "LOCALE_META", "isRTL", "direction", "SUGGEST_KEY", "localeSuggestionDismissed", "dismissLocaleSuggestion", "suggestedLocale", "pathname", "languages", "stored", "shown", "localeFromPathname", "preferred", "SUPPORTED_LOCALES", "localeFromLanguages", "t", "key", "vars", "locale", "currentLocale", "template", "dictionary", "CROSS_LOCALE_STRINGS", "match", "name", "SESSION_ACTIONS", "PRESENCE_DOT", "PRESENCE_WORD", "t", "MY_STATUS_OPTIONS", "shortLabelFromId", "id", "hex", "monoInitials", "label", "words", "a", "b", "statusSub", "status", "t", "emptySetup", "createSessionEntry", "opts", "id", "shortLabelFromId", "createInitialState", "patchSession", "state", "patch", "session", "sessionsReducer", "action", "A", "SESSION_ACTIONS", "entry", "sessions", "order", "x", "activeSessionId", "removedIdx", "next", "changed", "messages", "m", "byId", "byMid", "byFileId", "label", "decorateSession", "lastMessage", "s", "isUp", "isPending", "dot", "headerSub", "PRESENCE_DOT", "PRESENCE_WORD", "preview", "monoInitials", "decorateSessions", "GROUP_LIMITS", "CALL_ACTIONS", "MESH_KINDS", "MEMBER_OPS", "ENC", "GroupCryptoError", "message", "code", "fail", "msg", "toHex", "bytes", "view", "out", "i", "fromHex", "hex", "toB64", "binary", "fromB64", "b64", "max", "randomBytes", "n", "newGroupId", "lp", "label", "parts", "chunks", "total", "part", "header", "o", "c", "u32", "b", "equalBytes", "a", "diff", "assertGroupId", "groupId", "assertFingerprint", "fp", "assertEpoch", "epoch", "assertName", "name", "value", "canonicalFingerprints", "fps", "seen", "generateGroupIdentity", "subtle", "keyPair", "spki", "fingerprint", "fingerprintSpki", "importMemberIdentity", "publicKey", "buildCommitment", "nonce", "payload", "verifyCommitment", "commitment", "fields", "expected", "computeGroupSas", "contributions", "digits", "ordered", "ikm", "salt", "key", "bits", "dv", "GroupSasCeremony", "selfFingerprint", "memberFingerprints", "existing", "memberOpPayload", "op", "memberFps", "signMemberOp", "privateKey", "sig", "verifyMemberOp", "signature", "hashBody", "body", "groupMessagePayload", "seq", "senderFp", "bodyHash", "signGroupMessage", "verifyGroupMessage", "meshDescriptorPayload", "kind", "fromFp", "toFp", "descriptor", "signMeshDescriptor", "verifyMeshDescriptor", "linkProbePayload", "linkFp", "signLinkProbe", "verifyLinkProbe", "groupCallPayload", "callId", "action", "withVideo", "assertCallId", "newCallId", "signGroupCall", "verifyGroupCall", "GROUP_ACTIONS", "GROUP_PHASE", "MEMBER_STATE", "GROUP_PHASE_WORD", "t", "groupInitials", "name", "words", "a", "b", "createGroupEntry", "opts", "byFingerprint", "NAME_ENCODER", "clampNameBytes", "value", "out", "GROUP_LIMITS", "createInitialGroupState", "patchGroup", "state", "id", "patch", "group", "groupsReducer", "action", "A", "entry", "groups", "order", "x", "activeGroupId", "removedIdx", "changed", "members", "m", "next", "k", "messages", "linkedCount", "groupSub", "total", "linked", "groupDot", "decorateGroup", "lastMessage", "sub", "decorateGroups", "g", "GROUP_FRAMES", "GROUP_ENVELOPE", "GROUP_FRAME_TYPES", "isGroupFrame", "parsed", "groupFrameType", "encodeEnvelope", "frame", "json", "encoded", "toB64", "GROUP_LIMITS", "GroupSessionError", "decodeEnvelope", "envelope", "raw", "bytes", "fromB64", "TIMEOUTS", "MESH_MAX_CONCURRENT_DIALS", "MESH_MAX_ATTEMPTS", "message", "code", "GroupSession", "groupId", "name", "isAdmin", "subtle", "send", "emit", "mesh", "log", "assertGroupId", "assertName", "GROUP_PHASE", "newGroupId", "generateGroupIdentity", "MEMBER_STATE", "t", "sessionId", "fn", "ms", "phLine truncated
} }
+2 -2
View File
@@ -7,7 +7,7 @@ this document describes.
| | | | | |
| --- | --- | | --- | --- |
| Release | v6.8.6 | | Release | v6.8.8 |
| Protocol version | 4.1 | | Protocol version | 4.1 |
| Ratchet wire version | 1 | | Ratchet wire version | 1 |
@@ -347,5 +347,5 @@ no leg may stop a capture it borrowed.
## Scope ## Scope
This describes the browser implementation as it stands in v6.8.6. It is not a This describes the browser implementation as it stands in v6.8.8. It is not a
substitute for independent cryptographic review. substitute for independent cryptographic review.
+2 -2
View File
@@ -153,7 +153,7 @@
</thead> </thead>
<tbody><tr> <tbody><tr>
<td>Release</td> <td>Release</td>
<td>v6.8.6</td> <td>v6.8.8</td>
</tr> </tr>
<tr> <tr>
<td>Protocol version</td> <td>Protocol version</td>
@@ -486,7 +486,7 @@
shared across every leg, and stopped when the call, the group or the tab ends; shared across every leg, and stopped when the call, the group or the tab ends;
no leg may stop a capture it borrowed.</p> no leg may stop a capture it borrowed.</p>
<h2 id="scope">Scope</h2> <h2 id="scope">Scope</h2>
<p>This describes the browser implementation as it stands in v6.8.6. It is not a <p>This describes the browser implementation as it stands in v6.8.8. It is not a
substitute for independent cryptographic review.</p> substitute for independent cryptographic review.</p>
<nav class="more"> <nav class="more">
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/es/manifest.json"> <link rel="manifest" href="/es/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/es.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/es.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/fa/manifest.json"> <link rel="manifest" href="/fa/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/fa.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/fa.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/fr/manifest.json"> <link rel="manifest" href="/fr/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/fr.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/fr.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/he/manifest.json"> <link rel="manifest" href="/he/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/he.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/he.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/hi/manifest.json"> <link rel="manifest" href="/hi/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/hi.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/hi.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+18 -18
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,18 +287,18 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -486,12 +486,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/ko/manifest.json"> <link rel="manifest" href="/ko/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/ko.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/ko.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+12
View File
@@ -42,6 +42,18 @@
"theme.system": "النظام", "theme.system": "النظام",
"theme.light": "فاتح", "theme.light": "فاتح",
"theme.dark": "داكن", "theme.dark": "داكن",
"donate.eyebrow": "تبرّع بالعملات المشفّرة",
"donate.title": "ساعدنا في إبقاء SecureBit مستقلًا",
"donate.description": "يذهب كل تبرّع إلى التطوير وعمليات التدقيق الأمني.",
"donate.copy": "نسخ",
"donate.copied": "تم النسخ",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "شبكة Bitcoin",
"donate.net.eth": "الشبكة الرئيسية لـ Ethereum",
"donate.warn.usdt": "أرسل USDT على شبكة Tron (TRC-20) فقط.",
"donate.warn.btc": "أرسل BTC على شبكة Bitcoin فقط.",
"donate.warn.eth": "أرسل ETH على الشبكة الرئيسية لـ Ethereum فقط.",
"donate.qrAlt": "رمز QR لعنوان التبرّع بـ {coin}",
"community.title": "انضمّ إلى مستقبل الخصوصية", "community.title": "انضمّ إلى مستقبل الخصوصية",
"community.description": "ينمو SecureBit بفضل مجتمعه. أفكاركم وملاحظاتكم هي ما يصوغ مستقبل التواصل الآمن - مبنيّ في العلن، مع تحقّق ASN.1 كامل من طرف إلى طرف.", "community.description": "ينمو SecureBit بفضل مجتمعه. أفكاركم وملاحظاتكم هي ما يصوغ مستقبل التواصل الآمن - مبنيّ في العلن، مع تحقّق ASN.1 كامل من طرف إلى طرف.",
"community.github": "مستودع GitHub", "community.github": "مستودع GitHub",
+12
View File
@@ -42,6 +42,18 @@
"theme.system": "System", "theme.system": "System",
"theme.light": "Hell", "theme.light": "Hell",
"theme.dark": "Dunkel", "theme.dark": "Dunkel",
"donate.eyebrow": "Mit Krypto spenden",
"donate.title": "Halte SecureBit unabhängig",
"donate.description": "Jede Spende fließt in Entwicklung und Audits.",
"donate.copy": "Kopieren",
"donate.copied": "Kopiert",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Bitcoin-Netzwerk",
"donate.net.eth": "Ethereum-Mainnet",
"donate.warn.usdt": "Sende nur USDT über Tron (TRC-20).",
"donate.warn.btc": "Sende nur BTC über das Bitcoin-Netzwerk.",
"donate.warn.eth": "Sende nur ETH über das Ethereum-Mainnet.",
"donate.qrAlt": "QR-Code der {coin}-Spendenadresse",
"community.title": "Gestalten Sie die Zukunft der Privatsphäre mit", "community.title": "Gestalten Sie die Zukunft der Privatsphäre mit",
"community.description": "SecureBit wächst durch seine Community. Ihre Ideen und Ihr Feedback prägen die Zukunft sicherer Kommunikation - offen entwickelt, mit vollständiger ASN.1-Validierung von Ende zu Ende.", "community.description": "SecureBit wächst durch seine Community. Ihre Ideen und Ihr Feedback prägen die Zukunft sicherer Kommunikation - offen entwickelt, mit vollständiger ASN.1-Validierung von Ende zu Ende.",
"community.github": "GitHub-Repository", "community.github": "GitHub-Repository",
+12
View File
@@ -37,6 +37,18 @@
"theme.system": "System", "theme.system": "System",
"theme.light": "Light", "theme.light": "Light",
"theme.dark": "Dark", "theme.dark": "Dark",
"donate.eyebrow": "Donate in crypto",
"donate.title": "Keep SecureBit independent",
"donate.description": "Every donation goes to development and audits.",
"donate.copy": "Copy",
"donate.copied": "Copied",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Bitcoin network",
"donate.net.eth": "Ethereum mainnet",
"donate.warn.usdt": "Send only USDT on Tron (TRC-20).",
"donate.warn.btc": "Send only BTC on the Bitcoin network.",
"donate.warn.eth": "Send only ETH on Ethereum mainnet.",
"donate.qrAlt": "QR code of the {coin} donation address",
"community.title": "Join the future of privacy", "community.title": "Join the future of privacy",
"community.description": "SecureBit grows thanks to its community. Your ideas and feedback shape the future of secure communication - built in the open, with complete ASN.1 validation end-to-end.", "community.description": "SecureBit grows thanks to its community. Your ideas and feedback shape the future of secure communication - built in the open, with complete ASN.1 validation end-to-end.",
"community.github": "GitHub Repository", "community.github": "GitHub Repository",
+12
View File
@@ -42,6 +42,18 @@
"theme.system": "Sistema", "theme.system": "Sistema",
"theme.light": "Claro", "theme.light": "Claro",
"theme.dark": "Oscuro", "theme.dark": "Oscuro",
"donate.eyebrow": "Dona en cripto",
"donate.title": "Mantén SecureBit independiente",
"donate.description": "Cada donación se destina al desarrollo y a las auditorías.",
"donate.copy": "Copiar",
"donate.copied": "Copiado",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Red Bitcoin",
"donate.net.eth": "Red principal de Ethereum",
"donate.warn.usdt": "Envía solo USDT en Tron (TRC-20).",
"donate.warn.btc": "Envía solo BTC en la red Bitcoin.",
"donate.warn.eth": "Envía solo ETH en la red principal de Ethereum.",
"donate.qrAlt": "Código QR de la dirección de donación en {coin}",
"community.title": "Construye el futuro de la privacidad", "community.title": "Construye el futuro de la privacidad",
"community.description": "SecureBit crece gracias a su comunidad. Tus ideas y comentarios dan forma al futuro de la comunicación segura, desarrollada de forma abierta y con validación ASN.1 completa de extremo a extremo.", "community.description": "SecureBit crece gracias a su comunidad. Tus ideas y comentarios dan forma al futuro de la comunicación segura, desarrollada de forma abierta y con validación ASN.1 completa de extremo a extremo.",
"community.github": "Repositorio en GitHub", "community.github": "Repositorio en GitHub",
+12
View File
@@ -42,6 +42,18 @@
"theme.system": "سیستم", "theme.system": "سیستم",
"theme.light": "روشن", "theme.light": "روشن",
"theme.dark": "تیره", "theme.dark": "تیره",
"donate.eyebrow": "اهدای کمک با رمزارز",
"donate.title": "به مستقل ماندن SecureBit کمک کنید",
"donate.description": "هر کمک صرف توسعه و ممیزی‌های امنیتی می‌شود.",
"donate.copy": "کپی",
"donate.copied": "کپی شد",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "شبکه Bitcoin",
"donate.net.eth": "شبکه اصلی Ethereum",
"donate.warn.usdt": "فقط USDT را روی شبکه Tron (TRC-20) بفرستید.",
"donate.warn.btc": "فقط BTC را روی شبکه Bitcoin بفرستید.",
"donate.warn.eth": "فقط ETH را روی شبکه اصلی Ethereum بفرستید.",
"donate.qrAlt": "کد QR نشانی کمک با {coin}",
"community.title": "به آیندهٔ حریم خصوصی بپیوندید", "community.title": "به آیندهٔ حریم خصوصی بپیوندید",
"community.description": "‏SecureBit به‌لطف جامعه‌اش رشد می‌کند. ایده‌ها و بازخوردهای شما آیندهٔ ارتباط امن را شکل می‌دهد - ساخته‌شده در فضای باز، با اعتبارسنجی کامل ASN.1 از یک سر تا سر دیگر.", "community.description": "‏SecureBit به‌لطف جامعه‌اش رشد می‌کند. ایده‌ها و بازخوردهای شما آیندهٔ ارتباط امن را شکل می‌دهد - ساخته‌شده در فضای باز، با اعتبارسنجی کامل ASN.1 از یک سر تا سر دیگر.",
"community.github": "مخزن GitHub", "community.github": "مخزن GitHub",
+12
View File
@@ -37,6 +37,18 @@
"theme.system": "Système", "theme.system": "Système",
"theme.light": "Clair", "theme.light": "Clair",
"theme.dark": "Sombre", "theme.dark": "Sombre",
"donate.eyebrow": "Faire un don en crypto",
"donate.title": "Gardons SecureBit indépendant",
"donate.description": "Chaque don finance le développement et les audits.",
"donate.copy": "Copier",
"donate.copied": "Copié",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Réseau Bitcoin",
"donate.net.eth": "Réseau principal Ethereum",
"donate.warn.usdt": "Envoyez uniquement de l'USDT sur Tron (TRC-20).",
"donate.warn.btc": "Envoyez uniquement du BTC sur le réseau Bitcoin.",
"donate.warn.eth": "Envoyez uniquement de l'ETH sur le réseau principal Ethereum.",
"donate.qrAlt": "QR code de l'adresse de don {coin}",
"community.title": "Construisez l'avenir de la vie privée", "community.title": "Construisez l'avenir de la vie privée",
"community.description": "SecureBit grandit grâce à sa communauté. Vos idées et vos retours façonnent l'avenir des communications sécurisées, développées à ciel ouvert, avec une validation ASN.1 complète de bout en bout.", "community.description": "SecureBit grandit grâce à sa communauté. Vos idées et vos retours façonnent l'avenir des communications sécurisées, développées à ciel ouvert, avec une validation ASN.1 complète de bout en bout.",
"community.github": "Dépôt GitHub", "community.github": "Dépôt GitHub",
+12
View File
@@ -42,6 +42,18 @@
"theme.system": "מערכת", "theme.system": "מערכת",
"theme.light": "בהיר", "theme.light": "בהיר",
"theme.dark": "כהה", "theme.dark": "כהה",
"donate.eyebrow": "תרומה בקריפטו",
"donate.title": "עזרו לשמור על SecureBit עצמאית",
"donate.description": "כל תרומה מופנית לפיתוח ולביקורות אבטחה.",
"donate.copy": "העתקה",
"donate.copied": "הועתק",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "רשת Bitcoin",
"donate.net.eth": "הרשת הראשית של Ethereum",
"donate.warn.usdt": "שלחו USDT ברשת Tron (TRC-20) בלבד.",
"donate.warn.btc": "שלחו BTC ברשת Bitcoin בלבד.",
"donate.warn.eth": "שלחו ETH ברשת הראשית של Ethereum בלבד.",
"donate.qrAlt": "קוד QR של כתובת התרומה ב-{coin}",
"community.title": "הצטרפו לעתיד של הפרטיות", "community.title": "הצטרפו לעתיד של הפרטיות",
"community.description": "SecureBit גדל בזכות הקהילה שלו. הרעיונות והמשוב שלכם מעצבים את עתיד התקשורת המאובטחת - נבנה בגלוי, עם אימות ASN.1 מלא מקצה לקצה.", "community.description": "SecureBit גדל בזכות הקהילה שלו. הרעיונות והמשוב שלכם מעצבים את עתיד התקשורת המאובטחת - נבנה בגלוי, עם אימות ASN.1 מלא מקצה לקצה.",
"community.github": "מאגר GitHub", "community.github": "מאגר GitHub",
+12
View File
@@ -37,6 +37,18 @@
"theme.system": "सिस्टम", "theme.system": "सिस्टम",
"theme.light": "हल्की", "theme.light": "हल्की",
"theme.dark": "गहरी", "theme.dark": "गहरी",
"donate.eyebrow": "क्रिप्टो में दान करें",
"donate.title": "SecureBit को स्वतंत्र बनाए रखें",
"donate.description": "हर दान विकास और सुरक्षा ऑडिट पर खर्च होता है।",
"donate.copy": "कॉपी करें",
"donate.copied": "कॉपी हो गया",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Bitcoin नेटवर्क",
"donate.net.eth": "Ethereum मेननेट",
"donate.warn.usdt": "केवल Tron (TRC-20) पर USDT भेजें।",
"donate.warn.btc": "केवल Bitcoin नेटवर्क पर BTC भेजें।",
"donate.warn.eth": "केवल Ethereum मेननेट पर ETH भेजें।",
"donate.qrAlt": "{coin} दान पते का QR कोड",
"community.title": "निजता के भविष्य में शामिल हों", "community.title": "निजता के भविष्य में शामिल हों",
"community.description": "SecureBit अपने समुदाय से बढ़ता है। आपके विचार और सुझाव सुरक्षित संवाद का भविष्य गढ़ते हैं - खुले में बना, और पूरे रास्ते पूरी ASN.1 जाँच के साथ।", "community.description": "SecureBit अपने समुदाय से बढ़ता है। आपके विचार और सुझाव सुरक्षित संवाद का भविष्य गढ़ते हैं - खुले में बना, और पूरे रास्ते पूरी ASN.1 जाँच के साथ।",
"community.github": "GitHub रिपॉज़िटरी", "community.github": "GitHub रिपॉज़िटरी",
+12
View File
@@ -37,6 +37,18 @@
"theme.system": "시스템", "theme.system": "시스템",
"theme.light": "밝게", "theme.light": "밝게",
"theme.dark": "어둡게", "theme.dark": "어둡게",
"donate.eyebrow": "암호화폐로 후원하기",
"donate.title": "SecureBit의 독립을 지켜 주세요",
"donate.description": "모든 후원금은 개발과 보안 감사에 쓰입니다.",
"donate.copy": "복사",
"donate.copied": "복사됨",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "비트코인 네트워크",
"donate.net.eth": "이더리움 메인넷",
"donate.warn.usdt": "USDT는 Tron(TRC-20) 네트워크로만 보내세요.",
"donate.warn.btc": "BTC는 비트코인 네트워크로만 보내세요.",
"donate.warn.eth": "ETH는 이더리움 메인넷으로만 보내세요.",
"donate.qrAlt": "{coin} 후원 주소 QR 코드",
"community.title": "프라이버시의 미래를 함께 만듭니다", "community.title": "프라이버시의 미래를 함께 만듭니다",
"community.description": "SecureBit은 커뮤니티 덕분에 자랍니다. 여러분의 아이디어와 의견이 안전한 소통의 미래를 만듭니다 - 모든 과정이 공개되어 있고, 전 구간에서 ASN.1 검증을 수행합니다.", "community.description": "SecureBit은 커뮤니티 덕분에 자랍니다. 여러분의 아이디어와 의견이 안전한 소통의 미래를 만듭니다 - 모든 과정이 공개되어 있고, 전 구간에서 ASN.1 검증을 수행합니다.",
"community.github": "GitHub 저장소", "community.github": "GitHub 저장소",
+12
View File
@@ -42,6 +42,18 @@
"theme.system": "Системная", "theme.system": "Системная",
"theme.light": "Светлая", "theme.light": "Светлая",
"theme.dark": "Тёмная", "theme.dark": "Тёмная",
"donate.eyebrow": "Пожертвование в криптовалюте",
"donate.title": "Сохраним SecureBit независимым",
"donate.description": "Каждое пожертвование идёт на разработку и аудиты.",
"donate.copy": "Копировать",
"donate.copied": "Скопировано",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Сеть Bitcoin",
"donate.net.eth": "Основная сеть Ethereum",
"donate.warn.usdt": "Отправляйте только USDT в сети Tron (TRC-20).",
"donate.warn.btc": "Отправляйте только BTC в сети Bitcoin.",
"donate.warn.eth": "Отправляйте только ETH в основной сети Ethereum.",
"donate.qrAlt": "QR-код адреса для пожертвований в {coin}",
"community.title": "Присоединяйтесь к будущему приватности", "community.title": "Присоединяйтесь к будущему приватности",
"community.description": "SecureBit растёт благодаря сообществу. Ваши идеи и отзывы формируют будущее защищённого общения - разработка ведётся открыто, с полной проверкой ASN.1 на всём пути.", "community.description": "SecureBit растёт благодаря сообществу. Ваши идеи и отзывы формируют будущее защищённого общения - разработка ведётся открыто, с полной проверкой ASN.1 на всём пути.",
"community.github": "Репозиторий на GitHub", "community.github": "Репозиторий на GitHub",
+12
View File
@@ -42,6 +42,18 @@
"theme.system": "Системна", "theme.system": "Системна",
"theme.light": "Світла", "theme.light": "Світла",
"theme.dark": "Темна", "theme.dark": "Темна",
"donate.eyebrow": "Пожертва в криптовалюті",
"donate.title": "Збережімо SecureBit незалежним",
"donate.description": "Кожна пожертва йде на розробку та аудити.",
"donate.copy": "Копіювати",
"donate.copied": "Скопійовано",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Мережа Bitcoin",
"donate.net.eth": "Основна мережа Ethereum",
"donate.warn.usdt": "Надсилайте лише USDT у мережі Tron (TRC-20).",
"donate.warn.btc": "Надсилайте лише BTC у мережі Bitcoin.",
"donate.warn.eth": "Надсилайте лише ETH в основній мережі Ethereum.",
"donate.qrAlt": "QR-код адреси для пожертв у {coin}",
"community.title": "Долучайтеся до майбутнього приватності", "community.title": "Долучайтеся до майбутнього приватності",
"community.description": "SecureBit росте завдяки своїй спільноті. Ваші ідеї та відгуки формують майбутнє захищеного спілкування - розробка ведеться відкрито, з повною валідацією ASN.1 на всьому шляху.", "community.description": "SecureBit росте завдяки своїй спільноті. Ваші ідеї та відгуки формують майбутнє захищеного спілкування - розробка ведеться відкрито, з повною валідацією ASN.1 на всьому шляху.",
"community.github": "Репозиторій на GitHub", "community.github": "Репозиторій на GitHub",
+12
View File
@@ -42,6 +42,18 @@
"theme.system": "سسٹم", "theme.system": "سسٹم",
"theme.light": "روشن", "theme.light": "روشن",
"theme.dark": "گہرا", "theme.dark": "گہرا",
"donate.eyebrow": "کرپٹو میں عطیہ دیں",
"donate.title": "SecureBit کو خودمختار رکھیں",
"donate.description": "ہر عطیہ ڈیولپمنٹ اور سیکیورٹی آڈٹ پر خرچ ہوتا ہے۔",
"donate.copy": "کاپی کریں",
"donate.copied": "کاپی ہو گیا",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Bitcoin نیٹ ورک",
"donate.net.eth": "Ethereum مین نیٹ",
"donate.warn.usdt": "صرف Tron (TRC-20) پر USDT بھیجیں۔",
"donate.warn.btc": "صرف Bitcoin نیٹ ورک پر BTC بھیجیں۔",
"donate.warn.eth": "صرف Ethereum مین نیٹ پر ETH بھیجیں۔",
"donate.qrAlt": "{coin} عطیہ ایڈریس کا QR کوڈ",
"community.title": "نجیت کے مستقبل میں شامل ہوں", "community.title": "نجیت کے مستقبل میں شامل ہوں",
"community.description": "‏SecureBit اپنی برادری کی بدولت بڑھتا ہے۔ آپ کے خیالات اور آراء محفوظ رابطے کا مستقبل تشکیل دیتے ہیں - کھلے عام بنایا گیا، سرے سے سرے تک مکمل ASN.1 توثیق کے ساتھ۔", "community.description": "‏SecureBit اپنی برادری کی بدولت بڑھتا ہے۔ آپ کے خیالات اور آراء محفوظ رابطے کا مستقبل تشکیل دیتے ہیں - کھلے عام بنایا گیا، سرے سے سرے تک مکمل ASN.1 توثیق کے ساتھ۔",
"community.github": "‏GitHub مخزن", "community.github": "‏GitHub مخزن",
+12
View File
@@ -37,6 +37,18 @@
"theme.system": "跟随系统", "theme.system": "跟随系统",
"theme.light": "浅色", "theme.light": "浅色",
"theme.dark": "深色", "theme.dark": "深色",
"donate.eyebrow": "加密货币捐赠",
"donate.title": "让 SecureBit 保持独立",
"donate.description": "每一笔捐赠都用于开发和安全审计。",
"donate.copy": "复制",
"donate.copied": "已复制",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "比特币网络",
"donate.net.eth": "以太坊主网",
"donate.warn.usdt": "仅通过 Tron (TRC-20) 发送 USDT。",
"donate.warn.btc": "仅通过比特币网络发送 BTC。",
"donate.warn.eth": "仅通过以太坊主网发送 ETH。",
"donate.qrAlt": "{coin} 捐赠地址二维码",
"community.title": "一起构筑隐私的未来", "community.title": "一起构筑隐私的未来",
"community.description": "SecureBit 因社区而成长。你的想法与反馈塑造着安全通讯的未来 —— 开放开发,全程完整的 ASN.1 校验。", "community.description": "SecureBit 因社区而成长。你的想法与反馈塑造着安全通讯的未来 —— 开放开发,全程完整的 ASN.1 校验。",
"community.github": "GitHub 仓库", "community.github": "GitHub 仓库",
+7 -7
View File
@@ -1,10 +1,10 @@
{ {
"version": "1790219928897", "version": "1790225533889",
"buildVersion": "1790219928897", "buildVersion": "1790225533889",
"appVersion": "6.8.6", "appVersion": "6.8.8",
"buildTime": "2026-09-24T03:18:48.935Z", "buildTime": "2026-09-24T04:52:13.935Z",
"buildId": "1790219928897-02380a7", "buildId": "1790225533889-eac3009",
"gitHash": "02380a7", "gitHash": "eac3009",
"generated": true, "generated": true,
"generatedAt": "2026-09-24T03:18:48.936Z" "generatedAt": "2026-09-24T04:52:13.936Z"
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "securebit-chat", "name": "securebit-chat",
"version": "6.8.6", "version": "6.8.8",
"description": "Secure P2P Communication Application with End-to-End Encryption", "description": "Secure P2P Communication Application with End-to-End Encryption",
"main": "index.html", "main": "index.html",
"scripts": { "scripts": {
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/ru/manifest.json"> <link rel="manifest" href="/ru/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/ru.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/ru.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+22 -1
View File
@@ -38,6 +38,14 @@ const DIR = direction();
// someone who asked for less motion does not want. The jump still happens and // someone who asked for less motion does not want. The jump still happens and
// still lands in the same place — it just stops travelling to get there. // still lands in the same place — it just stops travelling to get there.
const scrollBehavior = () => (prefersReducedMotion() ? 'auto' : 'smooth'); const scrollBehavior = () => (prefersReducedMotion() ? 'auto' : 'smooth');
// After a chat ends, the start page scrolls to the donation card. The flag covers the
// usual case where the card only mounts once the chat is gone; the event covers a card
// that is already on screen. See src/components/ui/DonateCrypto.jsx.
const requestDonateScroll = () => {
window.__sbScrollToDonate = Date.now();
document.dispatchEvent(new CustomEvent('securebit:scroll-to-donate'));
};
import { GroupChatView, GroupSasModal, CreateGroupModal, GroupInviteModal, GroupErrorModal, AddMembersModal } from './components/ui/GroupChat.jsx'; import { GroupChatView, GroupSasModal, CreateGroupModal, GroupInviteModal, GroupErrorModal, AddMembersModal } from './components/ui/GroupChat.jsx';
import { GroupCallUI } from './components/ui/GroupCallUI.jsx'; import { GroupCallUI } from './components/ui/GroupCallUI.jsx';
import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js'; import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
@@ -1825,6 +1833,7 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
const uniqueSection = atIntro && h(UniqueFeatureSlider, { key: 'unique-features-slider' }); const uniqueSection = atIntro && h(UniqueFeatureSlider, { key: 'unique-features-slider' });
const partnersSection = atIntro && h(BecomePartner, { key: 'become-partner' }); const partnersSection = atIntro && h(BecomePartner, { key: 'become-partner' });
const roadmapSection = atIntro && h(Roadmap, { key: 'roadmap' }); const roadmapSection = atIntro && h(Roadmap, { key: 'roadmap' });
const donateSection = atIntro && h(DonateCrypto, { key: 'donate-crypto' });
const communitySection = atIntro && h(CommunityCTA, { key: 'community-cta' }); const communitySection = atIntro && h(CommunityCTA, { key: 'community-cta' });
// Ship the keyframes inside app.js so the animations can never go // Ship the keyframes inside app.js so the animations can never go
@@ -1870,7 +1879,7 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
return h('div', { className: 'sb-start', style: { flex: 1, minHeight: 0, width: '100%', display: 'flex', flexDirection: 'column', background: 'var(--sb-bg)', color: 'var(--sb-text-2)' } }, [keyframeStyle, rightPanel, qrModal]); return h('div', { className: 'sb-start', style: { flex: 1, minHeight: 0, width: '100%', display: 'flex', flexDirection: 'column', background: 'var(--sb-bg)', color: 'var(--sb-text-2)' } }, [keyframeStyle, rightPanel, qrModal]);
} }
return h('div', { className: 'sb-start', style: { width: '100%' } }, [keyframeStyle, hero, uniqueSection, partnersSection, roadmapSection, communitySection, qrModal]); return h('div', { className: 'sb-start', style: { width: '100%' } }, [keyframeStyle, hero, uniqueSection, partnersSection, roadmapSection, donateSection, communitySection, qrModal]);
}; };
// Global scroll function - defined outside components to ensure availability // Global scroll function - defined outside components to ensure availability
@@ -6880,6 +6889,7 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
const handleDisconnect = () => { const handleDisconnect = () => {
try { try {
const id = activeIdRef.current; const id = activeIdRef.current;
requestDonateScroll();
setSessionTimeLeft(0); setSessionTimeLeft(0);
// Global lifecycle events (consumed by any remaining listeners). // Global lifecycle events (consumed by any remaining listeners).
document.dispatchEvent(new CustomEvent('peer-disconnect')); document.dispatchEvent(new CustomEvent('peer-disconnect'));
@@ -6942,6 +6952,17 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
const isConnectedAndVerified = (connectionStatus === 'connected' || connectionStatus === 'verified' || connectionStatus === 'reconnecting') && isVerified; const isConnectedAndVerified = (connectionStatus === 'connected' || connectionStatus === 'verified' || connectionStatus === 'reconnecting') && isVerified;
// A chat that ends — the peer leaves, the link drops for good — lands
// back on the start page; bring the donation card into view there. Only
// for the SAME session going from chatting to not: switching to another
// tab's blank session is not an ending.
const prevChatRef = React.useRef({ id: activeSessionId, live: isConnectedAndVerified });
React.useEffect(() => {
const prev = prevChatRef.current;
prevChatRef.current = { id: activeSessionId, live: isConnectedAndVerified };
if (prev.live && !isConnectedAndVerified && prev.id === activeSessionId) requestDonateScroll();
}, [isConnectedAndVerified, activeSessionId]);
// The PWA "Install app" pill is a landing-page affordance — hide it once // The PWA "Install app" pill is a landing-page affordance — hide it once
// we're inside the chat (CSS: body.sb-in-chat #pwa-install-button). // we're inside the chat (CSS: body.sb-in-chat #pwa-install-button).
React.useEffect(() => { React.useEffect(() => {
+306
View File
@@ -0,0 +1,306 @@
import { t, isRTL } from '../../i18n/index.js';
// "Keep SecureBit independent" — crypto donation card.
// Translated from the Claude Design component (Crypto Donate v3.dc.html): coin switcher,
// address with copy, and a QR on the right.
//
// Everything is served from this origin. The design pulled the coin icons from jsDelivr
// and built the QR with a CDN script; the site's CSP (`img-src 'self' data:`) would block
// the first, and both would hand a third party the address of every visitor. The
// addresses are fixed, so the QRs are pre-rendered SVGs in /assets/crypto/.
const WALLETS = [
{ id: 'usdt', tab: 'USDT', coin: 'Tether USDT', badge: 'trx', address: 'TMopDoco5EwJB1QvQYsuhjUJdMvzPXNQYu' },
{ id: 'btc', tab: 'BTC', coin: 'Bitcoin', badge: null, address: 'bc1qnmc2xl7p0pxcy6lzf2tyf52wnslgzkgaq9kxpr' },
{ id: 'eth', tab: 'ETH', coin: 'Ethereum', badge: null, address: '0xCDfDfaD4EC1fa657C3d2A7EFabc1cb751bf1dAf7' }
];
const icon = (id) => `/assets/crypto/${id}.svg`;
// A disconnect asks for the page to be brought here (see handleDisconnect in app.jsx).
// The request is a timestamped flag rather than just an event, because the landing — and
// this card with it — usually mounts only after the chat has been torn down. It expires
// so that a request made while no landing was shown cannot fire minutes later.
const SCROLL_FLAG = '__sbScrollToDonate';
const SCROLL_EVENT = 'securebit:scroll-to-donate';
const SCROLL_TTL_MS = 5000;
const DonateCrypto = () => {
const [isMobile, setIsMobile] = React.useState(
typeof window !== 'undefined' && window.matchMedia('(max-width:767px)').matches
);
const [sel, setSel] = React.useState(0);
const [copied, setCopied] = React.useState(false);
// +1 when the new coin comes in from the end side, -1 from the start side; the QR
// slides in accordingly so a swipe feels like moving along the row of tabs.
const [slide, setSlide] = React.useState(0);
const touchStart = React.useRef(null);
const sectionRef = React.useRef(null);
const copyTimer = React.useRef(null);
React.useEffect(() => {
const mq = window.matchMedia('(max-width:767px)');
const onChange = () => setIsMobile(mq.matches);
mq.addEventListener ? mq.addEventListener('change', onChange) : mq.addListener(onChange);
return () => {
mq.removeEventListener ? mq.removeEventListener('change', onChange) : mq.removeListener(onChange);
clearTimeout(copyTimer.current);
};
}, []);
React.useEffect(() => {
let timer = null;
const scrollHere = () => {
const at = window[SCROLL_FLAG];
window[SCROLL_FLAG] = 0;
if (!at || Date.now() - at > SCROLL_TTL_MS) return;
clearTimeout(timer);
// Let the landing finish laying out first, or the target moves under the scroll.
timer = setTimeout(() => {
const el = sectionRef.current;
if (!el) return;
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
el.scrollIntoView({ behavior: reduce ? 'auto' : 'smooth', block: 'center' });
}, 350);
};
scrollHere();
document.addEventListener(SCROLL_EVENT, scrollHere);
return () => {
document.removeEventListener(SCROLL_EVENT, scrollHere);
clearTimeout(timer);
};
}, []);
const cur = WALLETS[sel];
const pick = (i) => {
const next = (i + WALLETS.length) % WALLETS.length;
if (next === sel) return;
setSlide(i > sel ? 1 : -1);
setSel(next);
setCopied(false);
};
// Swipe across the QR to change coin on a phone. Horizontal only, and only past a
// clear threshold, so a vertical scroll that starts on the QR still scrolls the page
// (touch-action: pan-y hands vertical pans to the browser). In a right-to-left locale
// the tab row runs the other way, and so does the swipe.
const onTouchStart = (e) => {
const p = e.touches[0];
touchStart.current = { x: p.clientX, y: p.clientY };
};
const onTouchEnd = (e) => {
const start = touchStart.current;
touchStart.current = null;
if (!start) return;
const p = e.changedTouches[0];
const dx = p.clientX - start.x;
const dy = p.clientY - start.y;
if (Math.abs(dx) < 40 || Math.abs(dx) < Math.abs(dy) * 1.5) return;
const forward = isRTL() ? dx > 0 : dx < 0;
pick(sel + (forward ? 1 : -1));
};
const copy = async () => {
try {
await navigator.clipboard.writeText(cur.address);
} catch (e) {
const ta = document.createElement('textarea');
ta.value = cur.address;
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); } catch (_) {}
ta.remove();
}
setCopied(true);
clearTimeout(copyTimer.current);
copyTimer.current = setTimeout(() => setCopied(false), 1800);
};
const SANS = "'Manrope', system-ui, -apple-system, sans-serif";
const MONO = "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace";
const FIELD = {
borderRadius: '14px', background: 'var(--sb-bg)', border: '1px solid rgba(var(--sb-ink), 0.07)'
};
const tabs = React.createElement('div', {
key: 'tabs', role: 'tablist',
style: { ...FIELD, display: 'flex', gap: '6px', padding: '5px', marginBottom: '14px' }
}, WALLETS.map((w, i) => {
const on = i === sel;
return React.createElement('button', {
key: w.id, type: 'button', role: 'tab', 'aria-selected': on,
onClick: () => pick(i),
style: {
flex: '1 1 0', minWidth: 0, display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
gap: '9px', padding: '11px 10px', borderRadius: '10px', border: 'none', cursor: 'pointer',
fontFamily: SANS, fontSize: '14px', fontWeight: 700, transition: 'all .18s',
background: on ? 'var(--sb-surface-2)' : 'transparent',
color: on ? 'var(--sb-text-1)' : 'var(--sb-text-7)',
boxShadow: on ? 'inset 0 0 0 1px rgba(var(--sb-orange-rgb), 0.45)' : 'none'
}
}, [
React.createElement('span', { key: 'ic', style: { position: 'relative', flex: 'none', width: '22px', height: '22px' } }, [
React.createElement('img', { key: 'logo', src: icon(w.id), alt: '', width: 22, height: 22, style: { display: 'block', width: '22px', height: '22px' } }),
w.badge && React.createElement('img', {
key: 'badge', src: icon(w.badge), alt: '', width: 11, height: 11,
style: {
position: 'absolute', right: '-3px', bottom: '-3px', width: '11px', height: '11px', borderRadius: '50%',
border: '1.5px solid var(--sb-surface-2)', background: 'var(--sb-surface-2)'
}
})
]),
w.tab
]);
}));
const copyBtn = React.createElement('button', {
key: 'copy', type: 'button', onClick: copy,
style: {
flex: 'none', display: 'inline-flex', alignItems: 'center', gap: '8px', padding: '12px 16px',
borderRadius: '10px', fontFamily: SANS, fontSize: '13.5px', fontWeight: 700, cursor: 'pointer',
background: 'rgba(var(--sb-ink), 0.05)', color: 'var(--sb-text-2)',
border: '1px solid rgba(var(--sb-ink), 0.1)', transition: 'all .18s'
},
onMouseEnter: (e) => { e.currentTarget.style.background = 'rgba(var(--sb-ink), 0.09)'; },
onMouseLeave: (e) => { e.currentTarget.style.background = 'rgba(var(--sb-ink), 0.05)'; }
}, [
copied
? React.createElement('svg', {
key: 'ok', width: 15, height: 15, viewBox: '0 0 24 24', fill: 'none', stroke: 'var(--sb-green-solid)',
strokeWidth: 2.4, strokeLinecap: 'round', strokeLinejoin: 'round',
dangerouslySetInnerHTML: { __html: '<path d="M5 12.5l4.2 4.2L19 6.5"/>' }
})
: React.createElement('svg', {
key: 'cp', width: 15, height: 15, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor',
strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round',
dangerouslySetInnerHTML: { __html: '<rect x="9" y="9" width="12" height="12" rx="2.5"/><path d="M5 15V5a2 2 0 0 1 2-2h8"/>' }
}),
React.createElement('span', { key: 'l', 'aria-live': 'polite' }, copied ? t('donate.copied') : t('donate.copy'))
]);
const addressField = React.createElement('div', {
key: 'addr',
style: { ...FIELD, display: 'flex', alignItems: 'center', gap: '12px', padding: '8px 8px 8px 18px' }
}, [
React.createElement('div', { key: 'tx', style: { flex: 1, minWidth: 0 } }, [
React.createElement('div', { key: 'net', style: { fontSize: '11.5px', fontWeight: 600, color: 'var(--sb-text-9)', marginBottom: '2px' } }, t(`donate.net.${cur.id}`)),
React.createElement('div', {
key: 'a', title: cur.address, dir: 'ltr',
style: { fontFamily: MONO, fontSize: '14px', color: 'var(--sb-text-2)', letterSpacing: '0.3px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
}, cur.address.slice(0, 8) + '…' + cur.address.slice(-8))
]),
copyBtn
]);
const warn = React.createElement('div', {
key: 'warn',
style: { display: 'flex', alignItems: 'center', gap: '7px', marginTop: '14px', fontSize: '12px', color: 'var(--sb-text-9)' }
}, [
React.createElement('svg', {
key: 'i', width: 13, height: 13, viewBox: '0 0 24 24', fill: 'none', stroke: 'var(--sb-orange-solid)',
strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', style: { flex: 'none' },
dangerouslySetInnerHTML: { __html: '<path d="M12 3l9.5 17h-19z"/><path d="M12 10v4M12 17.5v.01"/>' }
}),
t(`donate.warn.${cur.id}`)
]);
const left = React.createElement('div', {
key: 'left',
style: { flex: '1 1 440px', minWidth: 0, padding: isMobile ? '32px 22px 8px' : '44px', display: 'flex', flexDirection: 'column' }
}, [
React.createElement('div', {
key: 'eyebrow',
style: { fontFamily: MONO, fontSize: '11px', fontWeight: 600, color: 'var(--sb-text-9)', textTransform: 'uppercase', letterSpacing: '1.8px', marginBottom: '14px' }
}, t('donate.eyebrow')),
React.createElement('h2', {
key: 'title',
style: { margin: '0 0 10px', fontSize: isMobile ? '28px' : '34px', fontWeight: 800, letterSpacing: '-1px', lineHeight: 1.08, color: 'var(--sb-text-1)' }
}, t('donate.title')),
React.createElement('p', {
key: 'desc',
style: { margin: '0 0 30px', fontSize: '15px', lineHeight: 1.6, color: 'var(--sb-text-7)' }
}, t('donate.description')),
tabs,
addressField,
warn
]);
// The QR plate is white in both themes: scanners want dark modules on a light ground.
const dirSign = isRTL() ? -1 : 1;
const slideAnim = slide === 0 ? 'none'
: `${slide * dirSign > 0 ? 'cdInEnd' : 'cdInStart'} .28s cubic-bezier(.2,.7,.3,1)`;
const plate = React.createElement('div', {
key: 'plate',
onTouchStart, onTouchEnd,
style: {
position: 'relative', width: '232px', height: '232px', padding: '14px', borderRadius: '20px', background: '#fff',
boxShadow: '0 24px 60px rgba(var(--sb-shadow-rgb), calc(0.45 * var(--sb-shadow-k)))',
border: '1px solid rgba(var(--sb-ink), 0.07)', overflow: 'hidden', touchAction: 'pan-y', userSelect: 'none'
}
}, React.createElement('div', {
key: cur.id,
style: { position: 'relative', width: '100%', height: '100%', animation: slideAnim }
}, [
React.createElement('img', {
key: 'qr', src: `/assets/crypto/qr-${cur.id}.svg`, alt: t('donate.qrAlt', { coin: cur.coin }),
width: 204, height: 204, draggable: false,
style: { display: 'block', width: '100%', height: '100%', imageRendering: 'pixelated' }
}),
React.createElement('div', {
key: 'logo',
style: { position: 'absolute', left: '50%', top: '50%', transform: 'translate(-50%,-50%)', width: '48px', height: '48px', borderRadius: '12px', background: '#fff', display: 'grid', placeItems: 'center' }
}, React.createElement('img', { src: icon(cur.id), alt: '', width: 34, height: 34, draggable: false, style: { display: 'block', width: '34px', height: '34px' } }))
]));
// Page dots under the QR on a phone — the hint that it can be swiped.
const dots = isMobile && React.createElement('div', {
key: 'dots', 'aria-hidden': true,
style: { display: 'flex', justifyContent: 'center', gap: '7px', marginTop: '18px' }
}, WALLETS.map((w, i) => React.createElement('span', {
key: w.id,
style: {
width: i === sel ? '18px' : '7px', height: '7px', borderRadius: '4px', transition: 'all .25s',
background: i === sel ? 'var(--sb-orange-solid)' : 'rgba(var(--sb-ink), 0.16)'
}
})));
const right = React.createElement('div', {
key: 'right',
style: { flex: '0 0 auto', minWidth: isMobile ? 0 : '300px', margin: '0 auto', padding: isMobile ? '24px 22px 32px' : '44px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }
}, [plate, dots]);
const card = React.createElement('div', {
key: 'card',
style: {
width: '100%', maxWidth: '1000px', position: 'relative', overflow: 'hidden', borderRadius: '24px',
background: 'radial-gradient(520px 420px at 100% 50%, rgba(var(--sb-orange-rgb), 0.09), transparent 70%), var(--sb-surface)',
border: '1px solid rgba(var(--sb-ink), 0.07)', display: 'flex', flexWrap: 'wrap', alignItems: 'stretch',
animation: 'cdUp .45s cubic-bezier(.2,.7,.3,1)'
}
}, [
left,
right
]);
return React.createElement('section', {
id: 'donate', ref: sectionRef,
style: {
width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center',
background: 'var(--sb-bg)', color: 'var(--sb-text-2)', fontFamily: SANS,
padding: isMobile ? '48px 18px' : '64px 32px'
}
}, [
React.createElement('style', { key: 'kf', dangerouslySetInnerHTML: { __html: '@keyframes cdUp{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}' +
'@keyframes cdInEnd{from{opacity:0;transform:translateX(36px)}to{opacity:1;transform:none}}' +
'@keyframes cdInStart{from{opacity:0;transform:translateX(-36px)}to{opacity:1;transform:none}}' +
'@media (prefers-reduced-motion: reduce){#donate [style*="cdIn"]{animation:none!important}}' } }),
card
]);
};
DonateCrypto.SCROLL_FLAG = SCROLL_FLAG;
DonateCrypto.SCROLL_EVENT = SCROLL_EVENT;
window.DonateCrypto = DonateCrypto;
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "النظام", "theme.system": "النظام",
"theme.light": "فاتح", "theme.light": "فاتح",
"theme.dark": "داكن", "theme.dark": "داكن",
"donate.eyebrow": "تبرّع بالعملات المشفّرة",
"donate.title": "ساعدنا في إبقاء SecureBit مستقلًا",
"donate.description": "يذهب كل تبرّع إلى التطوير وعمليات التدقيق الأمني.",
"donate.copy": "نسخ",
"donate.copied": "تم النسخ",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "شبكة Bitcoin",
"donate.net.eth": "الشبكة الرئيسية لـ Ethereum",
"donate.warn.usdt": "أرسل USDT على شبكة Tron (TRC-20) فقط.",
"donate.warn.btc": "أرسل BTC على شبكة Bitcoin فقط.",
"donate.warn.eth": "أرسل ETH على الشبكة الرئيسية لـ Ethereum فقط.",
"donate.qrAlt": "رمز QR لعنوان التبرّع بـ {coin}",
"community.title": "انضمّ إلى مستقبل الخصوصية", "community.title": "انضمّ إلى مستقبل الخصوصية",
"community.description": "ينمو SecureBit بفضل مجتمعه. أفكاركم وملاحظاتكم هي ما يصوغ مستقبل التواصل الآمن - مبنيّ في العلن، مع تحقّق ASN.1 كامل من طرف إلى طرف.", "community.description": "ينمو SecureBit بفضل مجتمعه. أفكاركم وملاحظاتكم هي ما يصوغ مستقبل التواصل الآمن - مبنيّ في العلن، مع تحقّق ASN.1 كامل من طرف إلى طرف.",
"community.github": "مستودع GitHub", "community.github": "مستودع GitHub",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "System", "theme.system": "System",
"theme.light": "Hell", "theme.light": "Hell",
"theme.dark": "Dunkel", "theme.dark": "Dunkel",
"donate.eyebrow": "Mit Krypto spenden",
"donate.title": "Halte SecureBit unabhängig",
"donate.description": "Jede Spende fließt in Entwicklung und Audits.",
"donate.copy": "Kopieren",
"donate.copied": "Kopiert",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Bitcoin-Netzwerk",
"donate.net.eth": "Ethereum-Mainnet",
"donate.warn.usdt": "Sende nur USDT über Tron (TRC-20).",
"donate.warn.btc": "Sende nur BTC über das Bitcoin-Netzwerk.",
"donate.warn.eth": "Sende nur ETH über das Ethereum-Mainnet.",
"donate.qrAlt": "QR-Code der {coin}-Spendenadresse",
"community.title": "Gestalten Sie die Zukunft der Privatsphäre mit", "community.title": "Gestalten Sie die Zukunft der Privatsphäre mit",
"community.description": "SecureBit wächst durch seine Community. Ihre Ideen und Ihr Feedback prägen die Zukunft sicherer Kommunikation - offen entwickelt, mit vollständiger ASN.1-Validierung von Ende zu Ende.", "community.description": "SecureBit wächst durch seine Community. Ihre Ideen und Ihr Feedback prägen die Zukunft sicherer Kommunikation - offen entwickelt, mit vollständiger ASN.1-Validierung von Ende zu Ende.",
"community.github": "GitHub-Repository", "community.github": "GitHub-Repository",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "System", "theme.system": "System",
"theme.light": "Light", "theme.light": "Light",
"theme.dark": "Dark", "theme.dark": "Dark",
"donate.eyebrow": "Donate in crypto",
"donate.title": "Keep SecureBit independent",
"donate.description": "Every donation goes to development and audits.",
"donate.copy": "Copy",
"donate.copied": "Copied",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Bitcoin network",
"donate.net.eth": "Ethereum mainnet",
"donate.warn.usdt": "Send only USDT on Tron (TRC-20).",
"donate.warn.btc": "Send only BTC on the Bitcoin network.",
"donate.warn.eth": "Send only ETH on Ethereum mainnet.",
"donate.qrAlt": "QR code of the {coin} donation address",
"community.title": "Join the future of privacy", "community.title": "Join the future of privacy",
"community.description": "SecureBit grows thanks to its community. Your ideas and feedback shape the future of secure communication - built in the open, with complete ASN.1 validation end-to-end.", "community.description": "SecureBit grows thanks to its community. Your ideas and feedback shape the future of secure communication - built in the open, with complete ASN.1 validation end-to-end.",
"community.github": "GitHub Repository", "community.github": "GitHub Repository",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "Sistema", "theme.system": "Sistema",
"theme.light": "Claro", "theme.light": "Claro",
"theme.dark": "Oscuro", "theme.dark": "Oscuro",
"donate.eyebrow": "Dona en cripto",
"donate.title": "Mantén SecureBit independiente",
"donate.description": "Cada donación se destina al desarrollo y a las auditorías.",
"donate.copy": "Copiar",
"donate.copied": "Copiado",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Red Bitcoin",
"donate.net.eth": "Red principal de Ethereum",
"donate.warn.usdt": "Envía solo USDT en Tron (TRC-20).",
"donate.warn.btc": "Envía solo BTC en la red Bitcoin.",
"donate.warn.eth": "Envía solo ETH en la red principal de Ethereum.",
"donate.qrAlt": "Código QR de la dirección de donación en {coin}",
"community.title": "Construye el futuro de la privacidad", "community.title": "Construye el futuro de la privacidad",
"community.description": "SecureBit crece gracias a su comunidad. Tus ideas y comentarios dan forma al futuro de la comunicación segura, desarrollada de forma abierta y con validación ASN.1 completa de extremo a extremo.", "community.description": "SecureBit crece gracias a su comunidad. Tus ideas y comentarios dan forma al futuro de la comunicación segura, desarrollada de forma abierta y con validación ASN.1 completa de extremo a extremo.",
"community.github": "Repositorio en GitHub", "community.github": "Repositorio en GitHub",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "سیستم", "theme.system": "سیستم",
"theme.light": "روشن", "theme.light": "روشن",
"theme.dark": "تیره", "theme.dark": "تیره",
"donate.eyebrow": "اهدای کمک با رمزارز",
"donate.title": "به مستقل ماندن SecureBit کمک کنید",
"donate.description": "هر کمک صرف توسعه و ممیزی‌های امنیتی می‌شود.",
"donate.copy": "کپی",
"donate.copied": "کپی شد",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "شبکه Bitcoin",
"donate.net.eth": "شبکه اصلی Ethereum",
"donate.warn.usdt": "فقط USDT را روی شبکه Tron (TRC-20) بفرستید.",
"donate.warn.btc": "فقط BTC را روی شبکه Bitcoin بفرستید.",
"donate.warn.eth": "فقط ETH را روی شبکه اصلی Ethereum بفرستید.",
"donate.qrAlt": "کد QR نشانی کمک با {coin}",
"community.title": "به آیندهٔ حریم خصوصی بپیوندید", "community.title": "به آیندهٔ حریم خصوصی بپیوندید",
"community.description": "‏SecureBit به‌لطف جامعه‌اش رشد می‌کند. ایده‌ها و بازخوردهای شما آیندهٔ ارتباط امن را شکل می‌دهد - ساخته‌شده در فضای باز، با اعتبارسنجی کامل ASN.1 از یک سر تا سر دیگر.", "community.description": "‏SecureBit به‌لطف جامعه‌اش رشد می‌کند. ایده‌ها و بازخوردهای شما آیندهٔ ارتباط امن را شکل می‌دهد - ساخته‌شده در فضای باز، با اعتبارسنجی کامل ASN.1 از یک سر تا سر دیگر.",
"community.github": "مخزن GitHub", "community.github": "مخزن GitHub",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "Système", "theme.system": "Système",
"theme.light": "Clair", "theme.light": "Clair",
"theme.dark": "Sombre", "theme.dark": "Sombre",
"donate.eyebrow": "Faire un don en crypto",
"donate.title": "Gardons SecureBit indépendant",
"donate.description": "Chaque don finance le développement et les audits.",
"donate.copy": "Copier",
"donate.copied": "Copié",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Réseau Bitcoin",
"donate.net.eth": "Réseau principal Ethereum",
"donate.warn.usdt": "Envoyez uniquement de l'USDT sur Tron (TRC-20).",
"donate.warn.btc": "Envoyez uniquement du BTC sur le réseau Bitcoin.",
"donate.warn.eth": "Envoyez uniquement de l'ETH sur le réseau principal Ethereum.",
"donate.qrAlt": "QR code de l'adresse de don {coin}",
"community.title": "Construisez l'avenir de la vie privée", "community.title": "Construisez l'avenir de la vie privée",
"community.description": "SecureBit grandit grâce à sa communauté. Vos idées et vos retours façonnent l'avenir des communications sécurisées, développées à ciel ouvert, avec une validation ASN.1 complète de bout en bout.", "community.description": "SecureBit grandit grâce à sa communauté. Vos idées et vos retours façonnent l'avenir des communications sécurisées, développées à ciel ouvert, avec une validation ASN.1 complète de bout en bout.",
"community.github": "Dépôt GitHub", "community.github": "Dépôt GitHub",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "מערכת", "theme.system": "מערכת",
"theme.light": "בהיר", "theme.light": "בהיר",
"theme.dark": "כהה", "theme.dark": "כהה",
"donate.eyebrow": "תרומה בקריפטו",
"donate.title": "עזרו לשמור על SecureBit עצמאית",
"donate.description": "כל תרומה מופנית לפיתוח ולביקורות אבטחה.",
"donate.copy": "העתקה",
"donate.copied": "הועתק",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "רשת Bitcoin",
"donate.net.eth": "הרשת הראשית של Ethereum",
"donate.warn.usdt": "שלחו USDT ברשת Tron (TRC-20) בלבד.",
"donate.warn.btc": "שלחו BTC ברשת Bitcoin בלבד.",
"donate.warn.eth": "שלחו ETH ברשת הראשית של Ethereum בלבד.",
"donate.qrAlt": "קוד QR של כתובת התרומה ב-{coin}",
"community.title": "הצטרפו לעתיד של הפרטיות", "community.title": "הצטרפו לעתיד של הפרטיות",
"community.description": "SecureBit גדל בזכות הקהילה שלו. הרעיונות והמשוב שלכם מעצבים את עתיד התקשורת המאובטחת - נבנה בגלוי, עם אימות ASN.1 מלא מקצה לקצה.", "community.description": "SecureBit גדל בזכות הקהילה שלו. הרעיונות והמשוב שלכם מעצבים את עתיד התקשורת המאובטחת - נבנה בגלוי, עם אימות ASN.1 מלא מקצה לקצה.",
"community.github": "מאגר GitHub", "community.github": "מאגר GitHub",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "सिस्टम", "theme.system": "सिस्टम",
"theme.light": "हल्की", "theme.light": "हल्की",
"theme.dark": "गहरी", "theme.dark": "गहरी",
"donate.eyebrow": "क्रिप्टो में दान करें",
"donate.title": "SecureBit को स्वतंत्र बनाए रखें",
"donate.description": "हर दान विकास और सुरक्षा ऑडिट पर खर्च होता है।",
"donate.copy": "कॉपी करें",
"donate.copied": "कॉपी हो गया",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Bitcoin नेटवर्क",
"donate.net.eth": "Ethereum मेननेट",
"donate.warn.usdt": "केवल Tron (TRC-20) पर USDT भेजें।",
"donate.warn.btc": "केवल Bitcoin नेटवर्क पर BTC भेजें।",
"donate.warn.eth": "केवल Ethereum मेननेट पर ETH भेजें।",
"donate.qrAlt": "{coin} दान पते का QR कोड",
"community.title": "निजता के भविष्य में शामिल हों", "community.title": "निजता के भविष्य में शामिल हों",
"community.description": "SecureBit अपने समुदाय से बढ़ता है। आपके विचार और सुझाव सुरक्षित संवाद का भविष्य गढ़ते हैं - खुले में बना, और पूरे रास्ते पूरी ASN.1 जाँच के साथ।", "community.description": "SecureBit अपने समुदाय से बढ़ता है। आपके विचार और सुझाव सुरक्षित संवाद का भविष्य गढ़ते हैं - खुले में बना, और पूरे रास्ते पूरी ASN.1 जाँच के साथ।",
"community.github": "GitHub रिपॉज़िटरी", "community.github": "GitHub रिपॉज़िटरी",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "시스템", "theme.system": "시스템",
"theme.light": "밝게", "theme.light": "밝게",
"theme.dark": "어둡게", "theme.dark": "어둡게",
"donate.eyebrow": "암호화폐로 후원하기",
"donate.title": "SecureBit의 독립을 지켜 주세요",
"donate.description": "모든 후원금은 개발과 보안 감사에 쓰입니다.",
"donate.copy": "복사",
"donate.copied": "복사됨",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "비트코인 네트워크",
"donate.net.eth": "이더리움 메인넷",
"donate.warn.usdt": "USDT는 Tron(TRC-20) 네트워크로만 보내세요.",
"donate.warn.btc": "BTC는 비트코인 네트워크로만 보내세요.",
"donate.warn.eth": "ETH는 이더리움 메인넷으로만 보내세요.",
"donate.qrAlt": "{coin} 후원 주소 QR 코드",
"community.title": "프라이버시의 미래를 함께 만듭니다", "community.title": "프라이버시의 미래를 함께 만듭니다",
"community.description": "SecureBit은 커뮤니티 덕분에 자랍니다. 여러분의 아이디어와 의견이 안전한 소통의 미래를 만듭니다 - 모든 과정이 공개되어 있고, 전 구간에서 ASN.1 검증을 수행합니다.", "community.description": "SecureBit은 커뮤니티 덕분에 자랍니다. 여러분의 아이디어와 의견이 안전한 소통의 미래를 만듭니다 - 모든 과정이 공개되어 있고, 전 구간에서 ASN.1 검증을 수행합니다.",
"community.github": "GitHub 저장소", "community.github": "GitHub 저장소",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "Системная", "theme.system": "Системная",
"theme.light": "Светлая", "theme.light": "Светлая",
"theme.dark": "Тёмная", "theme.dark": "Тёмная",
"donate.eyebrow": "Пожертвование в криптовалюте",
"donate.title": "Сохраним SecureBit независимым",
"donate.description": "Каждое пожертвование идёт на разработку и аудиты.",
"donate.copy": "Копировать",
"donate.copied": "Скопировано",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Сеть Bitcoin",
"donate.net.eth": "Основная сеть Ethereum",
"donate.warn.usdt": "Отправляйте только USDT в сети Tron (TRC-20).",
"donate.warn.btc": "Отправляйте только BTC в сети Bitcoin.",
"donate.warn.eth": "Отправляйте только ETH в основной сети Ethereum.",
"donate.qrAlt": "QR-код адреса для пожертвований в {coin}",
"community.title": "Присоединяйтесь к будущему приватности", "community.title": "Присоединяйтесь к будущему приватности",
"community.description": "SecureBit растёт благодаря сообществу. Ваши идеи и отзывы формируют будущее защищённого общения - разработка ведётся открыто, с полной проверкой ASN.1 на всём пути.", "community.description": "SecureBit растёт благодаря сообществу. Ваши идеи и отзывы формируют будущее защищённого общения - разработка ведётся открыто, с полной проверкой ASN.1 на всём пути.",
"community.github": "Репозиторий на GitHub", "community.github": "Репозиторий на GitHub",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "Системна", "theme.system": "Системна",
"theme.light": "Світла", "theme.light": "Світла",
"theme.dark": "Темна", "theme.dark": "Темна",
"donate.eyebrow": "Пожертва в криптовалюті",
"donate.title": "Збережімо SecureBit незалежним",
"donate.description": "Кожна пожертва йде на розробку та аудити.",
"donate.copy": "Копіювати",
"donate.copied": "Скопійовано",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Мережа Bitcoin",
"donate.net.eth": "Основна мережа Ethereum",
"donate.warn.usdt": "Надсилайте лише USDT у мережі Tron (TRC-20).",
"donate.warn.btc": "Надсилайте лише BTC у мережі Bitcoin.",
"donate.warn.eth": "Надсилайте лише ETH в основній мережі Ethereum.",
"donate.qrAlt": "QR-код адреси для пожертв у {coin}",
"community.title": "Долучайтеся до майбутнього приватності", "community.title": "Долучайтеся до майбутнього приватності",
"community.description": "SecureBit росте завдяки своїй спільноті. Ваші ідеї та відгуки формують майбутнє захищеного спілкування - розробка ведеться відкрито, з повною валідацією ASN.1 на всьому шляху.", "community.description": "SecureBit росте завдяки своїй спільноті. Ваші ідеї та відгуки формують майбутнє захищеного спілкування - розробка ведеться відкрито, з повною валідацією ASN.1 на всьому шляху.",
"community.github": "Репозиторій на GitHub", "community.github": "Репозиторій на GitHub",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "سسٹم", "theme.system": "سسٹم",
"theme.light": "روشن", "theme.light": "روشن",
"theme.dark": "گہرا", "theme.dark": "گہرا",
"donate.eyebrow": "کرپٹو میں عطیہ دیں",
"donate.title": "SecureBit کو خودمختار رکھیں",
"donate.description": "ہر عطیہ ڈیولپمنٹ اور سیکیورٹی آڈٹ پر خرچ ہوتا ہے۔",
"donate.copy": "کاپی کریں",
"donate.copied": "کاپی ہو گیا",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "Bitcoin نیٹ ورک",
"donate.net.eth": "Ethereum مین نیٹ",
"donate.warn.usdt": "صرف Tron (TRC-20) پر USDT بھیجیں۔",
"donate.warn.btc": "صرف Bitcoin نیٹ ورک پر BTC بھیجیں۔",
"donate.warn.eth": "صرف Ethereum مین نیٹ پر ETH بھیجیں۔",
"donate.qrAlt": "{coin} عطیہ ایڈریس کا QR کوڈ",
"community.title": "نجیت کے مستقبل میں شامل ہوں", "community.title": "نجیت کے مستقبل میں شامل ہوں",
"community.description": "‏SecureBit اپنی برادری کی بدولت بڑھتا ہے۔ آپ کے خیالات اور آراء محفوظ رابطے کا مستقبل تشکیل دیتے ہیں - کھلے عام بنایا گیا، سرے سے سرے تک مکمل ASN.1 توثیق کے ساتھ۔", "community.description": "‏SecureBit اپنی برادری کی بدولت بڑھتا ہے۔ آپ کے خیالات اور آراء محفوظ رابطے کا مستقبل تشکیل دیتے ہیں - کھلے عام بنایا گیا، سرے سے سرے تک مکمل ASN.1 توثیق کے ساتھ۔",
"community.github": "‏GitHub مخزن", "community.github": "‏GitHub مخزن",
+12
View File
@@ -10,6 +10,18 @@ export const DICTIONARY = {
"theme.system": "跟随系统", "theme.system": "跟随系统",
"theme.light": "浅色", "theme.light": "浅色",
"theme.dark": "深色", "theme.dark": "深色",
"donate.eyebrow": "加密货币捐赠",
"donate.title": "让 SecureBit 保持独立",
"donate.description": "每一笔捐赠都用于开发和安全审计。",
"donate.copy": "复制",
"donate.copied": "已复制",
"donate.net.usdt": "USDT · Tron (TRC-20)",
"donate.net.btc": "比特币网络",
"donate.net.eth": "以太坊主网",
"donate.warn.usdt": "仅通过 Tron (TRC-20) 发送 USDT。",
"donate.warn.btc": "仅通过比特币网络发送 BTC。",
"donate.warn.eth": "仅通过以太坊主网发送 ETH。",
"donate.qrAlt": "{coin} 捐赠地址二维码",
"community.title": "一起构筑隐私的未来", "community.title": "一起构筑隐私的未来",
"community.description": "SecureBit 因社区而成长。你的想法与反馈塑造着安全通讯的未来 —— 开放开发,全程完整的 ASN.1 校验。", "community.description": "SecureBit 因社区而成长。你的想法与反馈塑造着安全通讯的未来 —— 开放开发,全程完整的 ASN.1 校验。",
"community.github": "GitHub 仓库", "community.github": "GitHub 仓库",
+1
View File
@@ -10,6 +10,7 @@ import '../components/ui/DownloadApps.jsx';
import '../components/ui/BecomePartner.jsx'; import '../components/ui/BecomePartner.jsx';
import '../components/ui/UniqueFeatureSlider.jsx'; import '../components/ui/UniqueFeatureSlider.jsx';
import '../components/ui/Roadmap.jsx'; import '../components/ui/Roadmap.jsx';
import '../components/ui/DonateCrypto.jsx';
import '../components/ui/CommunityCTA.jsx'; import '../components/ui/CommunityCTA.jsx';
import '../components/ui/FileTransfer.jsx'; import '../components/ui/FileTransfer.jsx';
import '../components/ui/IceServerSettings.jsx'; import '../components/ui/IceServerSettings.jsx';
+1 -1
View File
@@ -11,7 +11,7 @@ let DYNAMIC_CACHE = 'securebit-pwa-dynamic-v4.7.56';
// Build stamp — rewritten by scripts/post-build.js on every release so this file's // Build stamp — rewritten by scripts/post-build.js on every release so this file's
// bytes change each deploy. That is what makes the browser detect a new Service Worker, // bytes change each deploy. That is what makes the browser detect a new Service Worker,
// reinstall it, drop stale caches and (via controllerchange) prompt the page to update. // reinstall it, drop stale caches and (via controllerchange) prompt the page to update.
const SW_BUILD_VERSION = '1790219928897'; const SW_BUILD_VERSION = '1790225533889';
// Locale subdirectories, rewritten by scripts/build-i18n.js. Each localized page is a // Locale subdirectories, rewritten by scripts/build-i18n.js. Each localized page is a
// separate document at its own URL, so the shell has to be cached and served per // separate document at its own URL, so the shell has to be cached and served per
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/uk/manifest.json"> <link rel="manifest" href="/uk/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/uk.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/uk.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/ur/manifest.json"> <link rel="manifest" href="/ur/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/ur.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/ur.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest --> <!-- PWA Manifest -->
<link rel="manifest" href="/zh/manifest.json"> <link rel="manifest" href="/zh/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html> <!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
from localStorage while the parser is still in <head>, so the page paints in the from localStorage while the parser is still in <head>, so the page paints in the
chosen theme on the first frame instead of flashing dark and correcting itself. chosen theme on the first frame instead of flashing dark and correcting itself.
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). --> Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
<script src="/src/scripts/theme-boot.js?v=1790219928897"></script> <script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is <!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. --> is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790219928897"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
<!-- PWA Meta Tags --> <!-- PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -116,7 +116,7 @@
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png"> <link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
<!-- Apple Touch Icons --> <!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
@@ -125,7 +125,7 @@
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png"> <link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png"> <link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png"> <link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790219928897"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
<!-- Microsoft Tiles --> <!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35"> <meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts <!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
both execute in document order after parsing, so React still runs before the both execute in document order after parsing, so React still runs before the
app modules below, but the parser / first paint is no longer blocked. --> app modules below, but the parser / first paint is no longer blocked. -->
<script defer src="/config/ice-servers.js?v=1790219928897"></script> <script defer src="/config/ice-servers.js?v=1790225533889"></script>
<script defer src="/libs/react/react.production.min.js"></script> <script defer src="/libs/react/react.production.min.js"></script>
<script defer src="/libs/react-dom/react-dom.production.min.js"></script> <script defer src="/libs/react-dom/react-dom.production.min.js"></script>
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only — <!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
@@ -278,8 +278,8 @@
the end of <body>: 21 KB of CSS in total, but eight round trips before the the end of <body>: 21 KB of CSS in total, but eight round trips before the
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
connection. Bytes were never the problem; requests were. --> connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790219928897"> <link rel="stylesheet" href="/assets/app.css?v=1790225533889">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790219928897"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
<!-- Preload only the fonts needed for first paint. Inter is one variable file that <!-- Preload only the fonts needed for first paint. Inter is one variable file that
answers for every weight, so there is one to preload rather than the two of five answers for every weight, so there is one to preload rather than the two of five
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
@@ -287,19 +287,19 @@
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. --> <!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790219928897"></script> <script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
<noscript> <noscript>
<link rel="stylesheet" href="/libs/prism/prism.css"> <link rel="stylesheet" href="/libs/prism/prism.css">
</noscript> </noscript>
<script defer src="/src/scripts/fa-check.js?v=1790219928897"></script> <script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
<!-- This locale's dictionary, registered before anything can ask for a string. <!-- This locale's dictionary, registered before anything can ask for a string.
Only the default locale's is bundled (src/i18n/index.js imports it, because t() Only the default locale's is bundled (src/i18n/index.js imports it, because t()
falls back to it); every other page loads its own here instead of all thirteen falls back to it); every other page loads its own here instead of all thirteen
riding along inside dist/app.js. Absent on the default locale's own page. --> riding along inside dist/app.js. Absent on the default locale's own page. -->
<script type="module" src="/src/i18n/dict/zh.js?v=1790219928897"></script> <script type="module" src="/src/i18n/dict/zh.js?v=1790225533889"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790219928897"></script> <script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790219928897"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the <!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
app has mounted and the browser is idle, so the 142 KB it weighs is off the app has mounted and the browser is idle, so the 142 KB it weighs is off the
first load. src/components/QRScanner.js used to be here too and registered first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="/dist/app-boot.js?v=1790219928897"></script> <script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
<script type="module" src="/dist/app.js?v=1790219928897"></script> <script type="module" src="/dist/app.js?v=1790225533889"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
<script src="/src/pwa/install-prompt.js?v=1790219928897" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790219928897" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790219928897"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
</body> </body>
</html> </html>