v6.8.12: new design for exchanging invitations
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-27 16:23:03 -04:00
1 parent cd9d78d398
commit b0444a52c6
57 files changed
+919 -693

No files matched your search

+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## v6.8.12 — New design for exchanging invitations
New design for the screens where you exchange invitations and answers with your
peer, and for the safety code check. The download buttons now give you desktop
version 1.0.5.
## v6.8.8 — Project support section ## v6.8.8 — Project support section
Added a section to support the project. Added a section to support the project.
+2 -2
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.8-3ecf8e.svg)](CHANGELOG.md) [![Version](https://img.shields.io/badge/version-6.8.12-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)
@@ -31,7 +31,7 @@ From the web app at [securebit.chat](https://securebit.chat) — no install, no
| Open a channel | Verify the safety code | | Open a channel | Verify the safety code |
| :---: | :---: | | :---: | :---: |
| ![The landing page, with the option to create a channel or join one with an invitation](assets/screenshots/web-start.png) | ![The security verification screen, showing a seven-digit safety code to compare with the other person](assets/screenshots/web-verification.png) | | ![The landing page, with the option to create a channel or join one with an invitation](assets/screenshots/web-start.png) | ![The Verify your peer screen: a seven-digit safety code to read aloud, boxes to type it in, and You and Peer confirmation marks](assets/screenshots/web-verification.png) |
| Your device generates the keys and a one-time invitation. Nothing touches a server. | Compare the code out of band. Until both sides confirm, the chat stays locked. | | Your device generates the keys and a one-time invitation. Nothing touches a server. | Compare the code out of band. Until both sides confirm, the chat stays locked. |
| Encrypted conversation | Voice and video | | Encrypted conversation | Voice and video |
+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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/ar.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
</body> </body>
</html> </html>
+1 -1
View File
@@ -1 +1 @@
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(Line truncated *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(Line truncated
Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 98 KiB

+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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/de.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
</body> </body>
</html> </html>
+3 -3
View File
@@ -1,6 +1,6 @@
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 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 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 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
`+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 `+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
`:` `:`
@@ -42,7 +42,7 @@ Please wait for real-time cryptographic verification to complete.`,"sec.verifica
`,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 `,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 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 `),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()}
@@ -102,7 +102,7 @@ ${I.details||f("sec.verificationDone")}`,I.isRealData?D+=`
border: 1px solid var(--sb-surface-5); border: 1px solid var(--sb-surface-5);
`,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 `,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:`${U} - 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,_]);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 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 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(` `).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 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 `);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
/** /**
+2 -2
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/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"], "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": "2jBAAA,IAAAA,GAAAC,GAAA,CAAAC,GAAAC,KAAA,CASA,IAAMC,GAAN,KAAoC,CAClC,YAAYC,EAAS,CAAC,EAAG,CAEvB,KAAK,WAAc,OAAO,aAAiB,KAAe,cAAgB,OAAO,aAAa,YAAe,SACzG,aAAa,WACb,SACJ,KAAK,YAAc,KAAK,eAAe,EACvC,KAAK,YAAc,EACnB,KAAK,cAAgB,SAAS,MAC9B,KAAK,kBAAoB,CAAC,EAC1B,KAAK,aAAeA,EAAO,cAAgB,EAC3C,KAAK,YAAcA,EAAO,aAAe,IACzC,KAAK,qBAAuB,EAC5B,KAAK,eAAiBA,EAAO,gBAAkB,CAAC,EAGhD,KAAK,gBAAkB,OAAO,gBAG9B,KAAK,OAAS,KAAK,kBAAkB,EACrC,KAAK,iBAAmB,KAAK,yBAAyB,EAEtD,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,CAC1B,CAMA,oBAAqB,CAErB,CAOA,mBAAoB,CAClB,OAAI,OAAO,SAAS,OAAW,IACtB,SACE,OAAO,SAAS,SAAa,IAC/B,WACE,OAAO,SAAS,aAAiB,IACnC,eAEF,QACT,CAOA,0BAA2B,CACzB,OAAI,OAAO,SAAS,OAAW,IACtB,mBACE,OAAO,SAAS,SAAa,IAC/B,qBACE,OAAO,SAAS,aAAiB,IACnC,yBAEF,kBACT,CAOA,gBAAiB,CAEf,OAAI,KAAK,QAAU,OAAO,SAAS,KAAK,MAAM,EAAM,IAC3C,CAAC,SAAS,KAAK,MAAM,EAI1B,OAAO,SAAS,UAAa,WACxB,SAAS,SAAS,EAIpB,EACT,CAMA,wBAAyB,CAEnB,OAAO,SAAS,iBAAqB,KAAe,OAAO,SAAS,KAAK,MAAM,EAAM,KACvF,SAAS,iBAAiB,KAAK,iBAAkB,IAAM,CACrD,KAAK,YAAc,KAAK,eAAe,EAEnC,KAAK,cACP,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAEhC,CAAC,EAIH,OAAO,iBAAiB,QAAS,IAAM,CACrC,KAAK,YAAc,KAAK,eAAe,EACnC,KAAK,aACP,KAAK,iBAAiB,CAE1B,CAAC,EAED,OAAO,iBAAiB,OAAQ,IAAM,CACpC,KAAK,YAAc,KAAK,eAAe,CACzC,CAAC,EAGD,OAAO,iBAAiB,eAAgB,IAAM,CAC5C,KAAK,uBAAuB,CAC9B,CAAC,CACH,CAOA,MAAM,mBAAoB,CAExB,GAAI,CAAC,KAAK,iBAAmB,EAAE,iBAAkB,QAC/C,MAAO,GAGT,GAAI,KAAK,aAAe,UACtB,MAAO,GAGT,GAAI,KAAK,aAAe,SACtB,MAAO,GAGT,GAAI,CACF,YAAK,WAAa,MAAM,aAAa,kBAAkB,EAChD,KAAK,aAAe,SAC7B,MAAgB,CACd,MAAO,EACT,CACF,CAMA,aAAc,CACR,KAAK,YAAc,EACrB,SAAS,MAAQ,IAAI,KAAK,WAAW,KAAK,KAAK,aAAa,GAE5D,SAAS,MAAQ,KAAK,aAE1B,CAQA,aAAaC,EAAM,CACjB,GAAI,OAAOA,GAAS,SAClB,MAAO,GAIT,IAAMC,EAAM,SAAS,cAAc,KAAK,EACxC,OAAAA,EAAI,YAAcD,EACXC,EAAI,UACR,QAAQ,KAAM,MAAM,EACpB,QAAQ,KAAM,MAAM,EACpB,QAAQ,KAAM,QAAQ,EACtB,QAAQ,KAAM,QAAQ,EACtB,UAAU,EAAG,GAAG,CACrB,CAQA,gBAAgBC,EAAK,CACnB,GAAI,CAACA,EAAK,OAAO,KAEjB,GAAI,CACF,IAAMC,EAAY,IAAI,IAAID,EAAK,OAAO,SAAS,MAAM,EAGrD,OAAIC,EAAU,WAAa,UAAYA,EAAU,WAAa,QAExD,KAAK,eAAe,OAAS,EACb,KAAK,eAAe,KAAKC,GACzCD,EAAU,SAAWC,CACvB,EACmBD,EAAU,KAAO,KAE/BA,EAAU,KAGZ,IACT,MAAgB,CACd,OAAO,IACT,CACF,CAOA,gBAAiB,CACf,IAAME,EAAM,KAAK,IAAI,EACrB,OAAIA,EAAM,KAAK,qBAAuB,KAAK,YAClC,IAET,KAAK,qBAAuBA,EACrB,GACT,CASA,OAAOC,EAAYC,EAASC,EAAU,CAAC,EAAG,CAmBxC,GAjBI,OAAO,aAAiB,MAI5B,KAAK,YAAc,KAAK,eAAe,EAGnC,KAAK,cAKL,KAAK,aAAe,WAKpB,CAAC,KAAK,eAAe,EACvB,OAAO,KAIT,IAAMC,EAAiB,KAAK,aAAaH,GAAc,SAAS,EAC1DI,EAAc,KAAK,aAAaH,GAAW,EAAE,EAC7CI,EAAW,KAAK,gBAAgBH,EAAQ,IAAI,GAAK,yBAGnD,KAAK,kBAAkB,QAAU,KAAK,cACxC,KAAK,uBAAuB,EAG9B,GAAI,CAEF,IAAMI,EAAe,IAAI,aACvB,GAAGH,CAAc,GACjB,CACE,KAAMC,EAAY,UAAU,EAAG,GAAG,EAClC,KAAMC,EACN,MAAOA,EACP,IAAK,QAAQH,EAAQ,UAAY,SAAS,GAC1C,mBAAoB,GACpB,OAAQA,EAAQ,QAAU,GAE1B,QAAS,UAAU,QAAU,CAAC,IAAK,IAAK,GAAG,EAAI,OAE/C,KAAM,CACJ,SAAU,KAAK,aAAaA,EAAQ,QAAQ,EAC5C,UAAW,KAAK,IAAI,CAEtB,CACF,CACF,EAGA,KAAK,cACL,KAAK,YAAY,EAGjB,KAAK,kBAAkB,KAAKI,CAAY,EAGxCA,EAAa,QAAWC,GAAU,CAMhC,GALAA,EAAM,eAAe,EACrB,OAAO,MAAM,EACbD,EAAa,MAAM,EAGf,OAAOJ,EAAQ,SAAY,WAC7B,GAAI,CACFA,EAAQ,QAAQA,EAAQ,QAAQ,CAClC,OAASM,EAAO,CACd,QAAQ,MAAM,4CAA6CA,CAAK,CAClE,CAEJ,EAGAF,EAAa,QAAWC,GAAU,CAChC,QAAQ,MAAM,8CAA+CA,CAAK,CACpE,EAGA,IAAME,EAAmB,KAAK,IAAIP,EAAQ,WAAa,IAAM,GAAK,EAClE,kBAAW,IAAM,CACfI,EAAa,MAAM,EACnB,KAAK,gBAAgBA,CAAY,CACnC,EAAGG,CAAgB,EAEZH,CAET,OAASE,EAAO,CACd,eAAQ,MAAM,iDAAkDA,CAAK,EAC9D,IACT,CACF,CAOA,gBAAgBF,EAAc,CAC5B,IAAMI,EAAQ,KAAK,kBAAkB,QAAQJ,CAAY,EACrDI,EAAQ,IACV,KAAK,kBAAkB,OAAOA,EAAO,CAAC,CAE1C,CAKA,wBAAyB,CACvB,KAAK,kBAAkB,QAAQJ,GAAgB,CAC7C,GAAI,CACFA,EAAa,MAAM,CACrB,MAAgB,CAEhB,CACF,CAAC,EACD,KAAK,kBAAoB,CAAC,CAC5B,CAKA,kBAAmB,CACjB,KAAK,YAAc,EACnB,KAAK,YAAY,CACnB,CAMA,WAAY,CACV,MAAO,CACL,WAAY,KAAK,WACjB,YAAa,KAAK,YAClB,YAAa,KAAK,YAClB,gBAAiB,KAAK,gBACtB,UAAW,KAAK,kBAAkB,MACpC,CACF,CACF,EAKMK,GAAN,KAAoB,CAClB,aAAc,CACZ,KAAK,oBAAsB,IAAInB,GAA8B,CAC3D,aAAc,EACd,YAAa,IACb,eAAgB,CACd,OAAO,SAAS,MAElB,CACF,CAAC,EAED,KAAK,YAAc,KACnB,KAAK,eAAiB,KACtB,KAAK,eAAiB,OACtB,KAAK,eAAiB,CAAC,EACvB,KAAK,eAAiB,GACxB,CAKA,MAAM,MAAO,CAEb,CAMA,MAAM,qBAAsB,CAE1B,OADgB,MAAM,KAAK,oBAAoB,kBAAkB,CAEnE,CAMA,iBAAiBoB,EAAa,CAC5B,GAAI,CAACA,EAAa,CAChB,QAAQ,MAAM,4BAA4B,EAC1C,MACF,CAEA,KAAK,YAAcA,EAGnB,KAAK,YAAY,UAAaL,GAAU,CACtC,KAAK,sBAAsBA,EAAM,IAAI,CACvC,EAEA,KAAK,YAAY,QAAWC,GAAU,CAEtC,CACF,CAQA,gBAAgBK,EAAM,CACpB,GAAI,CACF,IAAMZ,EAAU,OAAOY,GAAS,SAAW,KAAK,MAAMA,CAAI,EAAIA,EAG9D,GAAI,CAACZ,GAAW,OAAOA,GAAY,SACjC,MAAM,IAAI,MAAM,2BAA2B,EAI7C,GAAI,CAACA,EAAQ,MAAQ,OAAOA,EAAQ,MAAS,SAC3C,MAAM,IAAI,MAAM,sBAAsB,EAIxC,GAAIA,EAAQ,KAAK,OAAS,IACxB,MAAM,IAAI,MAAM,kBAAkB,EAGpC,MAAO,CACL,KAAMA,EAAQ,KACd,WAAYA,EAAQ,YAAc,UAClC,SAAUA,EAAQ,UAAY,UAC9B,UAAWA,EAAQ,WAAa,KAAK,IAAI,EACzC,aAAcA,EAAQ,cAAgB,IACxC,CAEF,OAASO,EAAO,CACd,eAAQ,MAAM,oCAAqCA,CAAK,EACjD,IACT,CACF,CAOA,sBAAsBK,EAAM,CAC1B,IAAMZ,EAAU,KAAK,gBAAgBY,CAAI,EAEpCZ,IAKL,KAAK,eAAe,KAAKA,CAAO,EAC5B,KAAK,eAAe,OAAS,KAAK,gBACpC,KAAK,eAAe,MAAM,EAI5B,KAAK,eAAeA,CAAO,EAG3B,KAAK,oBAAoB,OACvBA,EAAQ,WACRA,EAAQ,KACR,CACE,KAAMA,EAAQ,aACd,SAAUA,EAAQ,SAClB,QAAUa,GAAa,CACrB,KAAK,sBAAsB,CAC7B,CACF,CACF,EAGK,KAAK,oBAAoB,aAC5B,KAAK,sBAAsB,EAE/B,CAOA,eAAeb,EAAS,CACtB,IAAMc,EAAY,SAAS,eAAeLine truncated "mappings": "2jBAAA,IAAAA,GAAAC,GAAA,CAAAC,GAAAC,KAAA,CASA,IAAMC,GAAN,KAAoC,CAClC,YAAYC,EAAS,CAAC,EAAG,CAEvB,KAAK,WAAc,OAAO,aAAiB,KAAe,cAAgB,OAAO,aAAa,YAAe,SACzG,aAAa,WACb,SACJ,KAAK,YAAc,KAAK,eAAe,EACvC,KAAK,YAAc,EACnB,KAAK,cAAgB,SAAS,MAC9B,KAAK,kBAAoB,CAAC,EAC1B,KAAK,aAAeA,EAAO,cAAgB,EAC3C,KAAK,YAAcA,EAAO,aAAe,IACzC,KAAK,qBAAuB,EAC5B,KAAK,eAAiBA,EAAO,gBAAkB,CAAC,EAGhD,KAAK,gBAAkB,OAAO,gBAG9B,KAAK,OAAS,KAAK,kBAAkB,EACrC,KAAK,iBAAmB,KAAK,yBAAyB,EAEtD,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,CAC1B,CAMA,oBAAqB,CAErB,CAOA,mBAAoB,CAClB,OAAI,OAAO,SAAS,OAAW,IACtB,SACE,OAAO,SAAS,SAAa,IAC/B,WACE,OAAO,SAAS,aAAiB,IACnC,eAEF,QACT,CAOA,0BAA2B,CACzB,OAAI,OAAO,SAAS,OAAW,IACtB,mBACE,OAAO,SAAS,SAAa,IAC/B,qBACE,OAAO,SAAS,aAAiB,IACnC,yBAEF,kBACT,CAOA,gBAAiB,CAEf,OAAI,KAAK,QAAU,OAAO,SAAS,KAAK,MAAM,EAAM,IAC3C,CAAC,SAAS,KAAK,MAAM,EAI1B,OAAO,SAAS,UAAa,WACxB,SAAS,SAAS,EAIpB,EACT,CAMA,wBAAyB,CAEnB,OAAO,SAAS,iBAAqB,KAAe,OAAO,SAAS,KAAK,MAAM,EAAM,KACvF,SAAS,iBAAiB,KAAK,iBAAkB,IAAM,CACrD,KAAK,YAAc,KAAK,eAAe,EAEnC,KAAK,cACP,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAEhC,CAAC,EAIH,OAAO,iBAAiB,QAAS,IAAM,CACrC,KAAK,YAAc,KAAK,eAAe,EACnC,KAAK,aACP,KAAK,iBAAiB,CAE1B,CAAC,EAED,OAAO,iBAAiB,OAAQ,IAAM,CACpC,KAAK,YAAc,KAAK,eAAe,CACzC,CAAC,EAGD,OAAO,iBAAiB,eAAgB,IAAM,CAC5C,KAAK,uBAAuB,CAC9B,CAAC,CACH,CAOA,MAAM,mBAAoB,CAExB,GAAI,CAAC,KAAK,iBAAmB,EAAE,iBAAkB,QAC/C,MAAO,GAGT,GAAI,KAAK,aAAe,UACtB,MAAO,GAGT,GAAI,KAAK,aAAe,SACtB,MAAO,GAGT,GAAI,CACF,YAAK,WAAa,MAAM,aAAa,kBAAkB,EAChD,KAAK,aAAe,SAC7B,MAAgB,CACd,MAAO,EACT,CACF,CAMA,aAAc,CACR,KAAK,YAAc,EACrB,SAAS,MAAQ,IAAI,KAAK,WAAW,KAAK,KAAK,aAAa,GAE5D,SAAS,MAAQ,KAAK,aAE1B,CAQA,aAAaC,EAAM,CACjB,GAAI,OAAOA,GAAS,SAClB,MAAO,GAIT,IAAMC,EAAM,SAAS,cAAc,KAAK,EACxC,OAAAA,EAAI,YAAcD,EACXC,EAAI,UACR,QAAQ,KAAM,MAAM,EACpB,QAAQ,KAAM,MAAM,EACpB,QAAQ,KAAM,QAAQ,EACtB,QAAQ,KAAM,QAAQ,EACtB,UAAU,EAAG,GAAG,CACrB,CAQA,gBAAgBC,EAAK,CACnB,GAAI,CAACA,EAAK,OAAO,KAEjB,GAAI,CACF,IAAMC,EAAY,IAAI,IAAID,EAAK,OAAO,SAAS,MAAM,EAGrD,OAAIC,EAAU,WAAa,UAAYA,EAAU,WAAa,QAExD,KAAK,eAAe,OAAS,EACb,KAAK,eAAe,KAAKC,GACzCD,EAAU,SAAWC,CACvB,EACmBD,EAAU,KAAO,KAE/BA,EAAU,KAGZ,IACT,MAAgB,CACd,OAAO,IACT,CACF,CAOA,gBAAiB,CACf,IAAME,EAAM,KAAK,IAAI,EACrB,OAAIA,EAAM,KAAK,qBAAuB,KAAK,YAClC,IAET,KAAK,qBAAuBA,EACrB,GACT,CASA,OAAOC,EAAYC,EAASC,EAAU,CAAC,EAAG,CAmBxC,GAjBI,OAAO,aAAiB,MAI5B,KAAK,YAAc,KAAK,eAAe,EAGnC,KAAK,cAKL,KAAK,aAAe,WAKpB,CAAC,KAAK,eAAe,EACvB,OAAO,KAIT,IAAMC,EAAiB,KAAK,aAAaH,GAAc,SAAS,EAC1DI,EAAc,KAAK,aAAaH,GAAW,EAAE,EAC7CI,EAAW,KAAK,gBAAgBH,EAAQ,IAAI,GAAK,yBAGnD,KAAK,kBAAkB,QAAU,KAAK,cACxC,KAAK,uBAAuB,EAG9B,GAAI,CAEF,IAAMI,EAAe,IAAI,aACvB,GAAGH,CAAc,GACjB,CACE,KAAMC,EAAY,UAAU,EAAG,GAAG,EAClC,KAAMC,EACN,MAAOA,EACP,IAAK,QAAQH,EAAQ,UAAY,SAAS,GAC1C,mBAAoB,GACpB,OAAQA,EAAQ,QAAU,GAE1B,QAAS,UAAU,QAAU,CAAC,IAAK,IAAK,GAAG,EAAI,OAE/C,KAAM,CACJ,SAAU,KAAK,aAAaA,EAAQ,QAAQ,EAC5C,UAAW,KAAK,IAAI,CAEtB,CACF,CACF,EAGA,KAAK,cACL,KAAK,YAAY,EAGjB,KAAK,kBAAkB,KAAKI,CAAY,EAGxCA,EAAa,QAAWC,GAAU,CAMhC,GALAA,EAAM,eAAe,EACrB,OAAO,MAAM,EACbD,EAAa,MAAM,EAGf,OAAOJ,EAAQ,SAAY,WAC7B,GAAI,CACFA,EAAQ,QAAQA,EAAQ,QAAQ,CAClC,OAASM,EAAO,CACd,QAAQ,MAAM,4CAA6CA,CAAK,CAClE,CAEJ,EAGAF,EAAa,QAAWC,GAAU,CAChC,QAAQ,MAAM,8CAA+CA,CAAK,CACpE,EAGA,IAAME,EAAmB,KAAK,IAAIP,EAAQ,WAAa,IAAM,GAAK,EAClE,kBAAW,IAAM,CACfI,EAAa,MAAM,EACnB,KAAK,gBAAgBA,CAAY,CACnC,EAAGG,CAAgB,EAEZH,CAET,OAASE,EAAO,CACd,eAAQ,MAAM,iDAAkDA,CAAK,EAC9D,IACT,CACF,CAOA,gBAAgBF,EAAc,CAC5B,IAAMI,EAAQ,KAAK,kBAAkB,QAAQJ,CAAY,EACrDI,EAAQ,IACV,KAAK,kBAAkB,OAAOA,EAAO,CAAC,CAE1C,CAKA,wBAAyB,CACvB,KAAK,kBAAkB,QAAQJ,GAAgB,CAC7C,GAAI,CACFA,EAAa,MAAM,CACrB,MAAgB,CAEhB,CACF,CAAC,EACD,KAAK,kBAAoB,CAAC,CAC5B,CAKA,kBAAmB,CACjB,KAAK,YAAc,EACnB,KAAK,YAAY,CACnB,CAMA,WAAY,CACV,MAAO,CACL,WAAY,KAAK,WACjB,YAAa,KAAK,YAClB,YAAa,KAAK,YAClB,gBAAiB,KAAK,gBACtB,UAAW,KAAK,kBAAkB,MACpC,CACF,CACF,EAKMK,GAAN,KAAoB,CAClB,aAAc,CACZ,KAAK,oBAAsB,IAAInB,GAA8B,CAC3D,aAAc,EACd,YAAa,IACb,eAAgB,CACd,OAAO,SAAS,MAElB,CACF,CAAC,EAED,KAAK,YAAc,KACnB,KAAK,eAAiB,KACtB,KAAK,eAAiB,OACtB,KAAK,eAAiB,CAAC,EACvB,KAAK,eAAiB,GACxB,CAKA,MAAM,MAAO,CAEb,CAMA,MAAM,qBAAsB,CAE1B,OADgB,MAAM,KAAK,oBAAoB,kBAAkB,CAEnE,CAMA,iBAAiBoB,EAAa,CAC5B,GAAI,CAACA,EAAa,CAChB,QAAQ,MAAM,4BAA4B,EAC1C,MACF,CAEA,KAAK,YAAcA,EAGnB,KAAK,YAAY,UAAaL,GAAU,CACtC,KAAK,sBAAsBA,EAAM,IAAI,CACvC,EAEA,KAAK,YAAY,QAAWC,GAAU,CAEtC,CACF,CAQA,gBAAgBK,EAAM,CACpB,GAAI,CACF,IAAMZ,EAAU,OAAOY,GAAS,SAAW,KAAK,MAAMA,CAAI,EAAIA,EAG9D,GAAI,CAACZ,GAAW,OAAOA,GAAY,SACjC,MAAM,IAAI,MAAM,2BAA2B,EAI7C,GAAI,CAACA,EAAQ,MAAQ,OAAOA,EAAQ,MAAS,SAC3C,MAAM,IAAI,MAAM,sBAAsB,EAIxC,GAAIA,EAAQ,KAAK,OAAS,IACxB,MAAM,IAAI,MAAM,kBAAkB,EAGpC,MAAO,CACL,KAAMA,EAAQ,KACd,WAAYA,EAAQ,YAAc,UAClC,SAAUA,EAAQ,UAAY,UAC9B,UAAWA,EAAQ,WAAa,KAAK,IAAI,EACzC,aAAcA,EAAQ,cAAgB,IACxC,CAEF,OAASO,EAAO,CACd,eAAQ,MAAM,oCAAqCA,CAAK,EACjD,IACT,CACF,CAOA,sBAAsBK,EAAM,CAC1B,IAAMZ,EAAU,KAAK,gBAAgBY,CAAI,EAEpCZ,IAKL,KAAK,eAAe,KAAKA,CAAO,EAC5B,KAAK,eAAe,OAAS,KAAK,gBACpC,KAAK,eAAe,MAAM,EAI5B,KAAK,eAAeA,CAAO,EAG3B,KAAK,oBAAoB,OACvBA,EAAQ,WACRA,EAAQ,KACR,CACE,KAAMA,EAAQ,aACd,SAAUA,EAAQ,SAClB,QAAUa,GAAa,CACrB,KAAK,sBAAsB,CAC7B,CACF,CACF,EAGK,KAAK,oBAAoB,aAC5B,KAAK,sBAAsB,EAE/B,CAOA,eAAeb,EAAS,CACtB,IAAMc,EAAY,SAAS,eAAeLine 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
+9 -9
View File
@@ -1,11 +1,11 @@
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 function qa(t=globalThis.window){return t?.SECUREBIT_DEBUG===!0}function js({targetWindow:t=globalThis.window,webrtcManagerRef:e,onClearData:n,clearConsole:r=()=>{typeof console.clear=="function"&&console.clear()}}){return qa(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 Ya="securebit-net";var ts="ice-device-key",rs="ice-settings";function ss(){return typeof indexedDB<"u"&&typeof crypto<"u"&&!!crypto.subtle}function is(){return new Promise((t,e)=>{let n;try{n=indexedDB.open(Ya,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 ns(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 $s(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 Ka(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 Qa(t){let e=await ns(t,ts);if(e instanceof CryptoKey)return e;let n=await crypto.subtle.generateKey({name:"AES-GCM",length:256},!1,["encrypt","decrypt"]);return await $s(t,ts,n),n}async function qs(t){if(!ss())throw new Error("Persistent storage is not available in this browser");let e=await is(),n=await Qa(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 $s(e,rs,{iv:Array.from(i),data:Array.from(new Uint8Array(c))})}async function Ys(){if(!ss())return null;try{let t=await is(),e=await ns(t,rs);if(!e||!Array.isArray(e.iv)||!Array.isArray(e.data))return null;let n=await ns(t,ts);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 as(){if(ss())try{let t=await is();await Ka(t,rs)}catch{}}var Xa="/api/turn-credentials",Xs=["turn.securebit.chat","144.172.96.126"];var Ks=[6e4,3e5,9e5];function Js(t){let e=/^turns?:([^:?\s]+)/i.exec(String(t||"").trim());return e?e[1].toLowerCase():null}function Ja(t){return!t||typeof t!="object"?!1:(Array.isArray(t.urls)?t.urls:[t.urls]).some(n=>Xs.includes(Js(n)))}function rr(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 Za(t,e){if(!Array.isArray(t)||!e||!rr(e.username)||!rr(e.credential))return!1;let n=!1;for(let r of t)Ja(r)&&(r.username=e.username,r.credential=e.credential,n=!0);if(!n&&Array.isArray(e.urls)){let r=e.urls.filter(i=>rr(i)&&Xs.includes(Js(i)));r.length>0&&(t.push({urls:r,username:e.username,credential:e.credential}),n=!0)}return n}function eo(t,e){let n=t&&Array.isArray(t.iceServers)?t.iceServers[0]:null;if(!n||!rr(n.username)||!rr(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 Qs=!1;function Zs(t={}){if(Qs||typeof window>"u"||typeof fetch!="function")return;Qs=!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(Xa,{method:"POST",cache:"no-store",credentials:"omit"});if(!m.ok)throw new Error(`HTTP ${m.status}`);let v=eo(await m.json(),Date.now()/1e3);if(!v)throw new Error("unusable response");Za(e(),v),n=v.expiry,r=v.ttl,c=0,d(Math.max(60,r/2)*1e3)}catch{let v=Ks[Math.min(c,Ks.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 Wn=["en","de","fr","es","uk","ru","zh","ko","hi","ar","he","fa","ur"],_r={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 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 `),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 tc=t=>()=>{if(t&&t.current){let e=()=>{t.current&&t.current.scrollTo({top:t.current.scrollHeight,behavior:Ur()})};e(),setTimeout(e,50),setTimeout(e,150),setTimeout(e,300),requestAnimationFrame(()=>{setTimeout(e,100)})}},ia=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=z=>String(z).replace(/[&<>"]/g,Q=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[Q]),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(([,z])=>z&&z.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}),D=z=>{let Q=a(`secTest.${z}`);return Q===`secTest.${z}`?le(z):Q},le=z=>{let Q=String(z).replace(/^verify/i,"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").trim();return Q=Q.replace(/\b(ecdh|ecdsa|aes|gcm|hmac|pfs|sas|mitm|asn|dtls|hkdf|spki|oid|p384)\b/gi,X=>X.toUpperCase()),Q.charAt(0).toUpperCase()+Q.slice(1)},ee='<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>',ge='<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>',we=k.map(([z,Q],X)=>{let W=!!(Q&&Q.passed),se=Q&&Q.details||(W?a("sec.testPassed"):a("sec.testFailed")),ce=a(`secDetail.${se}`),me=ce===`secDetail.${se}`?se:ce,L=W?"var(--sb-surface)":"var(--sb-bg)",$=W?"rgba(var(--sb-green-rgb), 0.16)":"rgba(var(--sb-red-rgb), 0.18)",ne=W?"rgba(var(--sb-green-rgb), 0.12)":"rgba(var(--sb-red-rgb), 0.1)",Me=W?"rgba(var(--sb-green-rgb), 0.26)":"rgba(var(--sb-red-rgb), 0.24)",q=W?"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 ${$}; animation:svIn .3s cubic-bezier(.2,.7,.3,1) both; animation-delay:${(X*.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 ${Me};">${W?ee:ge}</span>
<div style="flex:1; min-width:0;"> <div style="flex:1; min-width:0;">
<div style="font-size:14.5px; font-weight:700; letter-spacing:-0.2px; color:${q}; margin-bottom:3px;">${r(N(O))}</div> <div style="font-size:14.5px; font-weight:700; letter-spacing:-0.2px; color:${q}; margin-bottom:3px;">${r(D(z))}</div>
<div style="font-family:${n}; font-size:11.5px; line-height:1.45; color:var(--sb-text-7);">${r(ue)}</div> <div style="font-family:${n}; font-size:11.5px; line-height:1.45; color:var(--sb-text-7);">${r(me)}</div>
</div> </div>
</div>`}).join(""),C=document.createElement("div");C.id="sb-security-report",C.style.cssText="position:fixed; inset:0; z-index:10000; display:flex; align-items:center; justify-content:center; padding:24px; background:rgba(var(--sb-scrim-rgb), 0.62); backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px); font-family:'Manrope',system-ui,-apple-system,sans-serif; overflow:auto;",C.innerHTML=` </div>`}).join(""),C=document.createElement("div");C.id="sb-security-report",C.style.cssText="position:fixed; inset:0; z-index:10000; display:flex; align-items:center; justify-content:center; padding:24px; background:rgba(var(--sb-scrim-rgb), 0.62); backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px); font-family:'Manrope',system-ui,-apple-system,sans-serif; overflow:auto;",C.innerHTML=`
<style>@keyframes svIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes svPulse{0%,100%{opacity:1}50%{opacity:.4}}</style> <style>@keyframes svIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes svPulse{0%,100%{opacity:1}50%{opacity:.4}}</style>
@@ -42,10 +42,10 @@ Please wait for real-time cryptographic verification to complete.`);return}let n
Re-run Re-run
</button> </button>
</div> </div>
<div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(248px, 1fr)); gap:12px;">${ve}</div> <div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(248px, 1fr)); gap:12px;">${we}</div>
<div style="display:flex; align-items:center; gap:12px; margin-top:18px; padding:16px 20px; border-radius:14px; background:rgba(${d},0.06); border:1px solid rgba(${d},0.18);"> <div style="display:flex; align-items:center; gap:12px; margin-top:18px; padding:16px 20px; border-radius:14px; background:rgba(${d},0.06); border:1px solid rgba(${d},0.18);">
<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(),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 </div>`;let P=z=>{z.key==="Escape"&&j()},j=()=>{C.parentNode&&C.remove(),document.removeEventListener("keydown",P)};C.querySelector(".sv-close").addEventListener("click",j),C.addEventListener("click",z=>{z.target===C&&j()}),document.addEventListener("keydown",P);let O=C.querySelector(".sv-rerun");O.addEventListener("mouseenter",()=>{O.style.borderColor="rgba(var(--sb-orange-rgb), 0.45)",O.style.color="var(--sb-orange)"}),O.addEventListener("mouseleave",()=>{O.style.borderColor="rgba(var(--sb-ink), 0.1)",O.style.color="var(--sb-text-4)"}),O.addEventListener("click",()=>{j(),ia(t)}),document.body.appendChild(C)},nc=({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,xe=async()=>{try{if(!n)return;let Ee=null;typeof n.getRealSecurityLevel=="function"?Ee=await n.getRealSecurityLevel():typeof n.calculateAndReportSecurityLevel=="function"?Ee=await n.calculateAndReportSecurityLevel():window.EnhancedSecureCryptoUtils&&(Ee=await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(n)),q&&Ee&&Ee.isRealData!==!1&&p(Ee)}catch{}};xe();let Fe=Ee=>{q&&Ee.detail&&Ee.detail.securityData&&p(Ee.detail.securityData)};document.addEventListener("real-security-calculated",Fe);let at=setInterval(xe,15e3);return()=>{q=!1,clearInterval(at),document.removeEventListener("real-security-calculated",Fe)}},[n]);let D=t==="connected"||t==="verified",le=t==="disconnected"||t==="peer_disconnected",ee=D&&!i,ge=i||le?"var(--sb-red-solid)":D?"var(--sb-green-solid)":"var(--sb-yellow-solid)",we=i?a("chat.offline"):D?a("status.connected"):t==="peer_disconnected"?a("status.peerDisconnected"):t==="disconnected"?a("chat.disconnected"):a("status.connecting"),C=i||le?"0 0 0 3px rgba(var(--sb-red-rgb), 0.16)":D?"0 0 0 3px rgba(var(--sb-green-rgb), 0.16)":"0 0 0 3px rgba(var(--sb-yellow-rgb), 0.16)",P=D&&!i?ds[c]||"var(--sb-green-solid)":ge,j=D&&!i&&c&&ps[c]||null,O=()=>{B(r||""),y(!0)},z=()=>{typeof d=="function"&&d(I),y(!1)},Q=q=>{q.key==="Enter"?(q.preventDefault(),z()):q.key==="Escape"&&y(!1)},X=S&&Number.isFinite(S.passedChecks)?S.passedChecks:null,W=S&&Number.isFinite(S.totalChecks)?S.totalChecks:null,se=q=>{if(!q)return q;let xe=a(`secLevel.${q}`);return xe===`secLevel.${q}`?q:xe},ce=X!=null&&W?X+"/"+W:S?S.score+"%":"\u2014",me=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:()=>ia(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:me,fontSize:"13px"}}),React.createElement("span",{key:"l",className:"sb-sec-label",style:{fontSize:"13px",fontWeight:600,color:"var(--sb-text-2)"}},S?se(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)"}},ce),React.createElement("button",{key:"c",type:"button",title:a("sec.panelTitle"),onClick:q=>{q.stopPropagation(),v(xe=>!xe)},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"}}))]),$=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": "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 "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.8 | | Release | v6.8.12 |
| 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.8. It is not a This describes the browser implementation as it stands in v6.8.12. 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.8</td> <td>v6.8.12</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.8. It is not a <p>This describes the browser implementation as it stands in v6.8.12. 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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/es.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/fa.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/fr.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/he.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/hi.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/ko.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
</body> </body>
</html> </html>
+16 -12
View File
@@ -125,9 +125,10 @@
"intro.createDesc": "يولّد جهازك المفاتيح ودعوة تُستعمل مرّة واحدة. لا شيء يمرّ عبر خادم.", "intro.createDesc": "يولّد جهازك المفاتيح ودعوة تُستعمل مرّة واحدة. لا شيء يمرّ عبر خادم.",
"intro.createCta": "توليد المفاتيح والدعوة", "intro.createCta": "توليد المفاتيح والدعوة",
"intro.joinTitle": "انضمّ إلى قناة", "intro.joinTitle": "انضمّ إلى قناة",
"intro.joinDesc": "امسح رمز QR الخاص بالطرف الآخر بالكاميرا، أو الصق رمز الدعوة.",
"intro.scanTitle": "امسح رمز QR بالكاميرا", "intro.scanTitle": "امسح رمز QR بالكاميرا",
"intro.scanSubtitle": "الأسرع - وجّهها إلى شاشة الطرف الآخر", "intro.scanSubtitle": "الأسرع - وجّهها إلى شاشة الطرف الآخر",
"intro.orPasteCode": "أو ألصق الرمز", "intro.orPasteCode": "أو الصق الرمز",
"intro.pastePlaceholder": "ألصق رمز الدعوة هنا…", "intro.pastePlaceholder": "ألصق رمز الدعوة هنا…",
"intro.connect": "اتّصل", "intro.connect": "اتّصل",
"intro.connecting": "جارٍ المعالجة…", "intro.connecting": "جارٍ المعالجة…",
@@ -303,22 +304,26 @@
"handshake.establish": "إنشاء الاتصال", "handshake.establish": "إنشاء الاتصال",
"handshake.thenReceive": "ثمّ استقبل الردّ الذي يرسله الطرف الآخر", "handshake.thenReceive": "ثمّ استقبل الردّ الذي يرسله الطرف الآخر",
"handshake.pasteAnswerPlaceholder": "ألصق رمز ردّ الطرف الآخر…", "handshake.pasteAnswerPlaceholder": "ألصق رمز ردّ الطرف الآخر…",
"handshake.answerSentNote": "أرسل هذا الردّ إلى المُنشئ ثمّ انتظر - تُفتح الدردشة حالما يتّصل.", "handshake.answerSentNote": "بانتظار اتصال المُنشئ. ستُفتح المحادثة تلقائياً.",
"handshake.qrHint": "أبقِ هذا مفتوحاً حتى يلتقط الطرف الآخر الرمز.", "handshake.qrHint": "أبقِ هذا مفتوحاً حتى يلتقط الطرف الآخر الرمز.",
"handshake.qrHintFrames": "المصافحة موزّعة على {frames} إطارات - أبقِ هذا مفتوحاً حتى يلتقطها الطرف الآخر كلّها.", "handshake.qrHintFrames": "المصافحة موزّعة على {frames} إطارات - أبقِ هذا مفتوحاً حتى يلتقطها الطرف الآخر كلّها.",
"verify.title": "التحقّق الأمني",
"verify.desc": "قارِن رمز الأمان هذا مع الطرف الآخر عبر قناة منفصلة (مكالمة أو لقاء مباشر)، ثمّ اكتبه لفتح الدردشة.",
"verify.enterLabel": "أدخل الرمز المُتحقَّق منه",
"verify.placeholder": "اكتب الرمز هنا", "verify.placeholder": "اكتب الرمز هنا",
"verify.confirm": "تأكيد الرمز", "verify.confirm": "تأكيد الرمز",
"verify.confirmed": "مؤكَّد", "verify.confirmed": "مؤكَّد",
"verify.mismatch": "غير متطابقين",
"verify.yours": "تأكيدك",
"verify.peer": "تأكيد الطرف الآخر",
"verify.pending": "قيد الانتظار", "verify.pending": "قيد الانتظار",
"verify.waiting": "بانتظار الرمز…", "verify.waiting": "بانتظار الرمز…",
"verify.verified": "تمّ التحقّق من القناة", "sasv.title": "تحقّق من الطرف الآخر",
"verify.bothConfirmed": "أكّد الطرفان. جارٍ فتح الدردشة الآمنة…", "sasv.titleDone": "تم التحقّق من الطرف الآخر",
"sasv.desc": "اقرأ الرمز بصوت عالٍ عبر مكالمة أو وجهًا لوجه. لا تتابع إلا إذا رأى الطرف الآخر الأرقام نفسها.",
"sasv.descDone": "أكّدتما كلاكما الرمز نفسه. الاتصال موثَّق.",
"sasv.enter": "أدخل الرمز",
"sasv.match": "متطابق",
"sasv.noMatch": "غير متطابق",
"sasv.you": "أنت",
"sasv.peer": "الطرف الآخر",
"sasv.waitingPeer": "بانتظار الطرف الآخر…",
"sasv.codesDiffer": "الرموز غير متطابقة",
"sasv.opening": "جارٍ فتح المحادثة الآمنة",
"pwa.bannerTitle": "ثبّت SecureBit.chat", "pwa.bannerTitle": "ثبّت SecureBit.chat",
"pwa.bannerDesc": "احصل على تجربة تطبيق أصلي بأمان أعلى", "pwa.bannerDesc": "احصل على تجربة تطبيق أصلي بأمان أعلى",
"pwa.install": "تثبيت", "pwa.install": "تثبيت",
@@ -353,9 +358,8 @@
"action.scan": "مسح", "action.scan": "مسح",
"action.downloadDesktop": "نزّل تطبيق سطح المكتب", "action.downloadDesktop": "نزّل تطبيق سطح المكتب",
"action.advancedSettings": "إعدادات متقدّمة", "action.advancedSettings": "إعدادات متقدّمة",
"cred.offerTag": "عرض · أو انسخ النصّ",
"cred.answerTag": "ردّ · أو انسخ النصّ",
"cred.reveal": "اضغط للكشف - أبقِ هذا الرمز سرّياً", "cred.reveal": "اضغط للكشف - أبقِ هذا الرمز سرّياً",
"cred.orCopy": "أو انسخ الرمز",
"qr.title": "امسح رمز QR", "qr.title": "امسح رمز QR",
"qr.subtitle": "وجّه كاميرتك إلى رمزهم", "qr.subtitle": "وجّه كاميرتك إلى رمزهم",
"qr.scanning": "جارٍ المسح…", "qr.scanning": "جارٍ المسح…",
+16 -12
View File
@@ -125,9 +125,10 @@
"intro.createDesc": "Ihr Gerät erzeugt die Schlüssel und eine einmalige Einladung. Nichts davon berührt einen Server.", "intro.createDesc": "Ihr Gerät erzeugt die Schlüssel und eine einmalige Einladung. Nichts davon berührt einen Server.",
"intro.createCta": "Schlüssel & Einladung erzeugen", "intro.createCta": "Schlüssel & Einladung erzeugen",
"intro.joinTitle": "Kanal beitreten", "intro.joinTitle": "Kanal beitreten",
"intro.joinDesc": "Scannen Sie den QR-Code Ihres Gegenübers mit der Kamera oder fügen Sie seinen Einladungscode ein.",
"intro.scanTitle": "QR-Code mit der Kamera scannen", "intro.scanTitle": "QR-Code mit der Kamera scannen",
"intro.scanSubtitle": "Am schnellsten - auf den Bildschirm Ihres Gegenübers halten", "intro.scanSubtitle": "Am schnellsten - auf den Bildschirm Ihres Gegenübers halten",
"intro.orPasteCode": "oder Code einfügen", "intro.orPasteCode": "Oder den Code einfügen",
"intro.pastePlaceholder": "Einladungscode hier einfügen …", "intro.pastePlaceholder": "Einladungscode hier einfügen …",
"intro.connect": "Verbinden", "intro.connect": "Verbinden",
"intro.connecting": "Wird verarbeitet …", "intro.connecting": "Wird verarbeitet …",
@@ -303,22 +304,26 @@
"handshake.establish": "Verbindung herstellen", "handshake.establish": "Verbindung herstellen",
"handshake.thenReceive": "Nehmen Sie danach die Antwort Ihres Gegenübers entgegen", "handshake.thenReceive": "Nehmen Sie danach die Antwort Ihres Gegenübers entgegen",
"handshake.pasteAnswerPlaceholder": "Antwortcode des Gegenübers einfügen …", "handshake.pasteAnswerPlaceholder": "Antwortcode des Gegenübers einfügen …",
"handshake.answerSentNote": "Senden Sie diese Antwort an den Ersteller und warten Sie - der Chat öffnet sich, sobald er sich verbindet.", "handshake.answerSentNote": "Warten, bis sich der Ersteller verbindet. Der Chat öffnet sich automatisch.",
"handshake.qrHint": "Lassen Sie dies geöffnet, bis Ihr Gegenüber den Code erfasst hat.", "handshake.qrHint": "Lassen Sie dies geöffnet, bis Ihr Gegenüber den Code erfasst hat.",
"handshake.qrHintFrames": "Der Handschlag ist auf {frames} Einzelbilder verteilt - lassen Sie dies geöffnet, bis Ihr Gegenüber alle erfasst hat.", "handshake.qrHintFrames": "Der Handschlag ist auf {frames} Einzelbilder verteilt - lassen Sie dies geöffnet, bis Ihr Gegenüber alle erfasst hat.",
"verify.title": "Sicherheitsprüfung",
"verify.desc": "Vergleichen Sie diesen Sicherheitscode über einen anderen Weg mit Ihrem Gegenüber (Anruf oder persönlich) und geben Sie ihn dann ein, um den Chat freizuschalten.",
"verify.enterLabel": "Geprüften Code eingeben",
"verify.placeholder": "Code hier eingeben", "verify.placeholder": "Code hier eingeben",
"verify.confirm": "Code bestätigen", "verify.confirm": "Code bestätigen",
"verify.confirmed": "Bestätigt", "verify.confirmed": "Bestätigt",
"verify.mismatch": "Stimmt nicht überein",
"verify.yours": "Ihre Bestätigung",
"verify.peer": "Bestätigung des Gegenübers",
"verify.pending": "Ausstehend", "verify.pending": "Ausstehend",
"verify.waiting": "Warten auf den Code …", "verify.waiting": "Warten auf den Code …",
"verify.verified": "Kanal geprüft", "sasv.title": "Gegenüber überprüfen",
"verify.bothConfirmed": "Beide Seiten haben bestätigt. Der sichere Chat wird geöffnet …", "sasv.titleDone": "Gegenüber verifiziert",
"sasv.desc": "Lies den Code bei einem Anruf oder persönlich laut vor. Fahre nur fort, wenn dein Gegenüber dieselben Ziffern sieht.",
"sasv.descDone": "Ihr habt beide denselben Code bestätigt. Die Verbindung ist authentifiziert.",
"sasv.enter": "Code eingeben",
"sasv.match": "Stimmt überein",
"sasv.noMatch": "Stimmt nicht überein",
"sasv.you": "Du",
"sasv.peer": "Gegenüber",
"sasv.waitingPeer": "Warte auf Gegenüber…",
"sasv.codesDiffer": "Codes stimmen nicht überein",
"sasv.opening": "Sicherer Chat wird geöffnet",
"pwa.bannerTitle": "SecureBit.chat installieren", "pwa.bannerTitle": "SecureBit.chat installieren",
"pwa.bannerDesc": "Die App-Erfahrung mit zusätzlicher Sicherheit", "pwa.bannerDesc": "Die App-Erfahrung mit zusätzlicher Sicherheit",
"pwa.install": "Installieren", "pwa.install": "Installieren",
@@ -353,9 +358,8 @@
"action.scan": "Scannen", "action.scan": "Scannen",
"action.downloadDesktop": "Desktop-App laden", "action.downloadDesktop": "Desktop-App laden",
"action.advancedSettings": "Erweiterte Einstellungen", "action.advancedSettings": "Erweiterte Einstellungen",
"cred.offerTag": "Angebot · oder Text kopieren",
"cred.answerTag": "Antwort · oder Text kopieren",
"cred.reveal": "Zum Anzeigen klicken - halten Sie diesen Code geheim", "cred.reveal": "Zum Anzeigen klicken - halten Sie diesen Code geheim",
"cred.orCopy": "Oder den Code kopieren",
"qr.title": "QR-Code scannen", "qr.title": "QR-Code scannen",
"qr.subtitle": "Richten Sie die Kamera auf den QR-Code", "qr.subtitle": "Richten Sie die Kamera auf den QR-Code",
"qr.scanning": "Wird gescannt …", "qr.scanning": "Wird gescannt …",
+16 -12
View File
@@ -120,9 +120,10 @@
"intro.createDesc": "Your device generates the keys and a one-time invitation. Nothing touches a server.", "intro.createDesc": "Your device generates the keys and a one-time invitation. Nothing touches a server.",
"intro.createCta": "Generate keys & invitation", "intro.createCta": "Generate keys & invitation",
"intro.joinTitle": "Join a channel", "intro.joinTitle": "Join a channel",
"intro.joinDesc": "Scan your peer's QR with your camera, or paste their invitation code.",
"intro.scanTitle": "Scan QR with camera", "intro.scanTitle": "Scan QR with camera",
"intro.scanSubtitle": "Fastest - point at your peer's screen", "intro.scanSubtitle": "Fastest - point at your peer's screen",
"intro.orPasteCode": "or paste code", "intro.orPasteCode": "Or paste the code",
"intro.pastePlaceholder": "Paste invitation code here…", "intro.pastePlaceholder": "Paste invitation code here…",
"intro.connect": "Connect", "intro.connect": "Connect",
"intro.connecting": "Processing…", "intro.connecting": "Processing…",
@@ -298,22 +299,26 @@
"handshake.establish": "Establish connection", "handshake.establish": "Establish connection",
"handshake.thenReceive": "Then receive the answer your peer sends back", "handshake.thenReceive": "Then receive the answer your peer sends back",
"handshake.pasteAnswerPlaceholder": "Paste peer's answer code…", "handshake.pasteAnswerPlaceholder": "Paste peer's answer code…",
"handshake.answerSentNote": "Send this answer to the creator, then wait - the chat opens once they connect.", "handshake.answerSentNote": "Waiting for the creator to connect. The chat opens automatically.",
"handshake.qrHint": "Keep this open until your peer captures the code.", "handshake.qrHint": "Keep this open until your peer captures the code.",
"handshake.qrHintFrames": "The handshake is split across {frames} frames - keep this open until your peer captures all of them.", "handshake.qrHintFrames": "The handshake is split across {frames} frames - keep this open until your peer captures all of them.",
"verify.title": "Security verification",
"verify.desc": "Compare this safety code with your peer over a separate channel (voice / in person), then type it to unlock the chat.",
"verify.enterLabel": "Enter the verified code",
"verify.placeholder": "Type code here", "verify.placeholder": "Type code here",
"verify.confirm": "Confirm code", "verify.confirm": "Confirm code",
"verify.confirmed": "Confirmed", "verify.confirmed": "Confirmed",
"verify.mismatch": "Don't match",
"verify.yours": "Your confirmation",
"verify.peer": "Peer confirmation",
"verify.pending": "Pending", "verify.pending": "Pending",
"verify.waiting": "Waiting for code…", "verify.waiting": "Waiting for code…",
"verify.verified": "Channel verified", "sasv.title": "Verify your peer",
"verify.bothConfirmed": "Both parties confirmed. Opening the secure chat…", "sasv.titleDone": "Peer verified",
"sasv.desc": "Read the code aloud over a call or in person. Continue only if your peer sees the same digits.",
"sasv.descDone": "You both confirmed the same code. The connection is authenticated.",
"sasv.enter": "Enter the code",
"sasv.match": "Match",
"sasv.noMatch": "Doesn't match",
"sasv.you": "You",
"sasv.peer": "Peer",
"sasv.waitingPeer": "Waiting for peer…",
"sasv.codesDiffer": "Codes don't match",
"sasv.opening": "Opening secure chat",
"pwa.bannerTitle": "Install SecureBit.chat", "pwa.bannerTitle": "Install SecureBit.chat",
"pwa.bannerDesc": "Get the native app experience with enhanced security", "pwa.bannerDesc": "Get the native app experience with enhanced security",
"pwa.install": "Install", "pwa.install": "Install",
@@ -348,9 +353,8 @@
"action.scan": "Scan", "action.scan": "Scan",
"action.downloadDesktop": "Download desktop app", "action.downloadDesktop": "Download desktop app",
"action.advancedSettings": "Advanced settings", "action.advancedSettings": "Advanced settings",
"cred.offerTag": "offer · or copy text",
"cred.answerTag": "answer · or copy text",
"cred.reveal": "Click to reveal - keep this code private", "cred.reveal": "Click to reveal - keep this code private",
"cred.orCopy": "Or copy the code",
"qr.title": "Scan QR code", "qr.title": "Scan QR code",
"qr.subtitle": "Point your camera at their QR", "qr.subtitle": "Point your camera at their QR",
"qr.scanning": "Scanning…", "qr.scanning": "Scanning…",
+16 -12
View File
@@ -125,9 +125,10 @@
"intro.createDesc": "Tu dispositivo genera las claves y una invitación de un solo uso. Nada de esto pasa por un servidor.", "intro.createDesc": "Tu dispositivo genera las claves y una invitación de un solo uso. Nada de esto pasa por un servidor.",
"intro.createCta": "Generar claves e invitación", "intro.createCta": "Generar claves e invitación",
"intro.joinTitle": "Unirse a un canal", "intro.joinTitle": "Unirse a un canal",
"intro.joinDesc": "Escanea el QR de tu contacto con la cámara o pega su código de invitación.",
"intro.scanTitle": "Escanear el QR con la cámara", "intro.scanTitle": "Escanear el QR con la cámara",
"intro.scanSubtitle": "Lo más rápido: apunta a la pantalla de la otra persona", "intro.scanSubtitle": "Lo más rápido: apunta a la pantalla de la otra persona",
"intro.orPasteCode": "o pega el código", "intro.orPasteCode": "O pega el código",
"intro.pastePlaceholder": "Pega aquí el código de invitación…", "intro.pastePlaceholder": "Pega aquí el código de invitación…",
"intro.connect": "Conectar", "intro.connect": "Conectar",
"intro.connecting": "Procesando…", "intro.connecting": "Procesando…",
@@ -303,22 +304,26 @@
"handshake.establish": "Establecer la conexión", "handshake.establish": "Establecer la conexión",
"handshake.thenReceive": "Después recibe la respuesta que te devuelva la otra persona", "handshake.thenReceive": "Después recibe la respuesta que te devuelva la otra persona",
"handshake.pasteAnswerPlaceholder": "Pega aquí el código de respuesta…", "handshake.pasteAnswerPlaceholder": "Pega aquí el código de respuesta…",
"handshake.answerSentNote": "Envía esta respuesta a quien creó el canal y espera: el chat se abrirá cuando conecte.", "handshake.answerSentNote": "Esperando a que el creador se conecte. El chat se abrirá automáticamente.",
"handshake.qrHint": "Mantén esto abierto hasta que la otra persona capture el código.", "handshake.qrHint": "Mantén esto abierto hasta que la otra persona capture el código.",
"handshake.qrHintFrames": "El saludo se reparte en {frames} imágenes: mantén esto abierto hasta que las capture todas.", "handshake.qrHintFrames": "El saludo se reparte en {frames} imágenes: mantén esto abierto hasta que las capture todas.",
"verify.title": "Verificación de seguridad",
"verify.desc": "Compara este código de seguridad con la otra persona por otra vía (voz o en persona) y escríbelo para desbloquear el chat.",
"verify.enterLabel": "Introduce el código verificado",
"verify.placeholder": "Escribe el código aquí", "verify.placeholder": "Escribe el código aquí",
"verify.confirm": "Confirmar el código", "verify.confirm": "Confirmar el código",
"verify.confirmed": "Confirmado", "verify.confirmed": "Confirmado",
"verify.mismatch": "No coinciden",
"verify.yours": "Tu confirmación",
"verify.peer": "Confirmación de la otra persona",
"verify.pending": "Pendiente", "verify.pending": "Pendiente",
"verify.waiting": "Esperando el código…", "verify.waiting": "Esperando el código…",
"verify.verified": "Canal verificado", "sasv.title": "Verifica a tu contacto",
"verify.bothConfirmed": "Ambas partes han confirmado. Abriendo el chat seguro…", "sasv.titleDone": "Contacto verificado",
"sasv.desc": "Lee el código en voz alta por llamada o en persona. Continúa solo si tu contacto ve los mismos dígitos.",
"sasv.descDone": "Ambos confirmasteis el mismo código. La conexión está autenticada.",
"sasv.enter": "Introduce el código",
"sasv.match": "Coincide",
"sasv.noMatch": "No coincide",
"sasv.you": "Tú",
"sasv.peer": "Contacto",
"sasv.waitingPeer": "Esperando a tu contacto…",
"sasv.codesDiffer": "Los códigos no coinciden",
"sasv.opening": "Abriendo el chat seguro",
"pwa.bannerTitle": "Instala SecureBit.chat", "pwa.bannerTitle": "Instala SecureBit.chat",
"pwa.bannerDesc": "Disfruta de la experiencia de app nativa con más seguridad", "pwa.bannerDesc": "Disfruta de la experiencia de app nativa con más seguridad",
"pwa.install": "Instalar", "pwa.install": "Instalar",
@@ -353,9 +358,8 @@
"action.scan": "Escanear", "action.scan": "Escanear",
"action.downloadDesktop": "Descargar la app de escritorio", "action.downloadDesktop": "Descargar la app de escritorio",
"action.advancedSettings": "Ajustes avanzados", "action.advancedSettings": "Ajustes avanzados",
"cred.offerTag": "oferta · o copia el texto",
"cred.answerTag": "respuesta · o copia el texto",
"cred.reveal": "Haz clic para mostrarlo: mantén este código en privado", "cred.reveal": "Haz clic para mostrarlo: mantén este código en privado",
"cred.orCopy": "O copia el código",
"qr.title": "Escanear código QR", "qr.title": "Escanear código QR",
"qr.subtitle": "Apunta la cámara a su código QR", "qr.subtitle": "Apunta la cámara a su código QR",
"qr.scanning": "Escaneando…", "qr.scanning": "Escaneando…",
+16 -12
View File
@@ -125,9 +125,10 @@
"intro.createDesc": "دستگاه شما کلیدها و یک دعوت‌نامهٔ یک‌بارمصرف می‌سازد. هیچ‌چیز به سرور نمی‌رسد.", "intro.createDesc": "دستگاه شما کلیدها و یک دعوت‌نامهٔ یک‌بارمصرف می‌سازد. هیچ‌چیز به سرور نمی‌رسد.",
"intro.createCta": "ساخت کلید و دعوت‌نامه", "intro.createCta": "ساخت کلید و دعوت‌نامه",
"intro.joinTitle": "به کانالی بپیوندید", "intro.joinTitle": "به کانالی بپیوندید",
"intro.joinDesc": "QR طرف مقابل را با دوربین اسکن کنید یا کد دعوت او را جای‌گذاری کنید.",
"intro.scanTitle": "‏QR را با دوربین بخوانید", "intro.scanTitle": "‏QR را با دوربین بخوانید",
"intro.scanSubtitle": "سریع‌ترین راه - به صفحهٔ همتایتان بگیرید", "intro.scanSubtitle": "سریع‌ترین راه - به صفحهٔ همتایتان بگیرید",
"intro.orPasteCode": "یا کد را بچسبانید", "intro.orPasteCode": "یا کد را جای‌گذاری کنید",
"intro.pastePlaceholder": "کد دعوت را اینجا بچسبانید…", "intro.pastePlaceholder": "کد دعوت را اینجا بچسبانید…",
"intro.connect": "اتصال", "intro.connect": "اتصال",
"intro.connecting": "در حال پردازش…", "intro.connecting": "در حال پردازش…",
@@ -303,22 +304,26 @@
"handshake.establish": "برقراری اتصال", "handshake.establish": "برقراری اتصال",
"handshake.thenReceive": "سپس پاسخی را که همتایتان برمی‌گرداند دریافت کنید", "handshake.thenReceive": "سپس پاسخی را که همتایتان برمی‌گرداند دریافت کنید",
"handshake.pasteAnswerPlaceholder": "کد پاسخ همتا را بچسبانید…", "handshake.pasteAnswerPlaceholder": "کد پاسخ همتا را بچسبانید…",
"handshake.answerSentNote": "این پاسخ را برای سازنده بفرستید و منتظر بمانید - گفتگو به‌محض اتصال او باز می‌شود.", "handshake.answerSentNote": "در انتظار اتصال سازنده. گفتگو به‌طور خودکار باز می‌شود.",
"handshake.qrHint": "این را باز نگه دارید تا همتایتان کد را بگیرد.", "handshake.qrHint": "این را باز نگه دارید تا همتایتان کد را بگیرد.",
"handshake.qrHintFrames": "دست‌دادن میان {frames} فریم پخش شده است - این را باز نگه دارید تا همتایتان همهٔ آن‌ها را بگیرد.", "handshake.qrHintFrames": "دست‌دادن میان {frames} فریم پخش شده است - این را باز نگه دارید تا همتایتان همهٔ آن‌ها را بگیرد.",
"verify.title": "تأیید امنیتی",
"verify.desc": "این کد ایمنی را از راه کانالی جداگانه (تماس صوتی / حضوری) با همتایتان مقایسه کنید، سپس آن را بنویسید تا گفتگو باز شود.",
"verify.enterLabel": "کد تأییدشده را وارد کنید",
"verify.placeholder": "کد را اینجا بنویسید", "verify.placeholder": "کد را اینجا بنویسید",
"verify.confirm": "تأیید کد", "verify.confirm": "تأیید کد",
"verify.confirmed": "تأیید شد", "verify.confirmed": "تأیید شد",
"verify.mismatch": "هم‌خوان نیستند",
"verify.yours": "تأیید شما",
"verify.peer": "تأیید همتا",
"verify.pending": "در انتظار", "verify.pending": "در انتظار",
"verify.waiting": "در انتظار کد…", "verify.waiting": "در انتظار کد…",
"verify.verified": "کانال تأیید شد", "sasv.title": "طرف مقابل را تأیید کنید",
"verify.bothConfirmed": "هر دو طرف تأیید کردند. گفتگوی امن در حال باز شدن…", "sasv.titleDone": "طرف مقابل تأیید شد",
"sasv.desc": "کد را در تماس یا حضوری با صدای بلند بخوانید. فقط وقتی ادامه دهید که طرف مقابل همان ارقام را ببیند.",
"sasv.descDone": "هر دو یک کد را تأیید کردید. اتصال احراز هویت شد.",
"sasv.enter": "کد را وارد کنید",
"sasv.match": "مطابق است",
"sasv.noMatch": "مطابق نیست",
"sasv.you": "شما",
"sasv.peer": "طرف مقابل",
"sasv.waitingPeer": "در انتظار طرف مقابل…",
"sasv.codesDiffer": "کدها مطابق نیستند",
"sasv.opening": "در حال باز کردن گفتگوی امن",
"pwa.bannerTitle": "‏SecureBit.chat را نصب کنید", "pwa.bannerTitle": "‏SecureBit.chat را نصب کنید",
"pwa.bannerDesc": "تجربهٔ برنامهٔ بومی با امنیت بیشتر", "pwa.bannerDesc": "تجربهٔ برنامهٔ بومی با امنیت بیشتر",
"pwa.install": "نصب", "pwa.install": "نصب",
@@ -353,9 +358,8 @@
"action.scan": "خواندن", "action.scan": "خواندن",
"action.downloadDesktop": "دریافت برنامهٔ رومیزی", "action.downloadDesktop": "دریافت برنامهٔ رومیزی",
"action.advancedSettings": "تنظیمات پیشرفته", "action.advancedSettings": "تنظیمات پیشرفته",
"cred.offerTag": "پیشنهاد · یا متن را رونوشت کنید",
"cred.answerTag": "پاسخ · یا متن را رونوشت کنید",
"cred.reveal": "برای نمایش کلیک کنید - این کد را خصوصی نگه دارید", "cred.reveal": "برای نمایش کلیک کنید - این کد را خصوصی نگه دارید",
"cred.orCopy": "یا کد را رونوشت کنید",
"qr.title": "خواندن کد QR", "qr.title": "خواندن کد QR",
"qr.subtitle": "دوربین را به QR آن‌ها بگیرید", "qr.subtitle": "دوربین را به QR آن‌ها بگیرید",
"qr.scanning": "در حال خواندن…", "qr.scanning": "در حال خواندن…",
+16 -12
View File
@@ -120,9 +120,10 @@
"intro.createDesc": "Votre appareil génère les clés et une invitation à usage unique. Rien de tout cela ne passe par un serveur.", "intro.createDesc": "Votre appareil génère les clés et une invitation à usage unique. Rien de tout cela ne passe par un serveur.",
"intro.createCta": "Générer les clés et l'invitation", "intro.createCta": "Générer les clés et l'invitation",
"intro.joinTitle": "Rejoindre un canal", "intro.joinTitle": "Rejoindre un canal",
"intro.joinDesc": "Scannez le QR de votre correspondant avec l'appareil photo, ou collez son code d'invitation.",
"intro.scanTitle": "Scanner le QR avec la caméra", "intro.scanTitle": "Scanner le QR avec la caméra",
"intro.scanSubtitle": "Le plus rapide : visez l'écran de votre correspondant", "intro.scanSubtitle": "Le plus rapide : visez l'écran de votre correspondant",
"intro.orPasteCode": "ou collez le code", "intro.orPasteCode": "Ou collez le code",
"intro.pastePlaceholder": "Collez ici le code d'invitation…", "intro.pastePlaceholder": "Collez ici le code d'invitation…",
"intro.connect": "Se connecter", "intro.connect": "Se connecter",
"intro.connecting": "Traitement…", "intro.connecting": "Traitement…",
@@ -298,22 +299,26 @@
"handshake.establish": "Établir la connexion", "handshake.establish": "Établir la connexion",
"handshake.thenReceive": "Recevez ensuite la réponse que votre correspondant renvoie", "handshake.thenReceive": "Recevez ensuite la réponse que votre correspondant renvoie",
"handshake.pasteAnswerPlaceholder": "Collez le code de réponse…", "handshake.pasteAnswerPlaceholder": "Collez le code de réponse…",
"handshake.answerSentNote": "Envoyez cette réponse à la personne qui a créé le canal, puis patientez : la discussion s'ouvre dès qu'elle se connecte.", "handshake.answerSentNote": "En attente de la connexion du créateur. Le chat s'ouvrira automatiquement.",
"handshake.qrHint": "Gardez ceci ouvert jusqu'à ce que votre correspondant ait capturé le code.", "handshake.qrHint": "Gardez ceci ouvert jusqu'à ce que votre correspondant ait capturé le code.",
"handshake.qrHintFrames": "La poignée de main est répartie sur {frames} images : gardez ceci ouvert jusqu'à ce que votre correspondant les ait toutes capturées.", "handshake.qrHintFrames": "La poignée de main est répartie sur {frames} images : gardez ceci ouvert jusqu'à ce que votre correspondant les ait toutes capturées.",
"verify.title": "Vérification de sécurité",
"verify.desc": "Comparez ce code de sécurité avec votre correspondant par un autre canal (voix ou en personne), puis saisissez-le pour ouvrir la discussion.",
"verify.enterLabel": "Saisissez le code vérifié",
"verify.placeholder": "Tapez le code ici", "verify.placeholder": "Tapez le code ici",
"verify.confirm": "Confirmer le code", "verify.confirm": "Confirmer le code",
"verify.confirmed": "Confirmé", "verify.confirmed": "Confirmé",
"verify.mismatch": "Ne correspond pas",
"verify.yours": "Votre confirmation",
"verify.peer": "Confirmation du correspondant",
"verify.pending": "En attente", "verify.pending": "En attente",
"verify.waiting": "En attente du code…", "verify.waiting": "En attente du code…",
"verify.verified": "Canal vérifié", "sasv.title": "Vérifiez votre correspondant",
"verify.bothConfirmed": "Les deux parties ont confirmé. Ouverture de la discussion sécurisée…", "sasv.titleDone": "Correspondant vérifié",
"sasv.desc": "Lisez le code à voix haute par appel ou en personne. Ne continuez que si votre correspondant voit les mêmes chiffres.",
"sasv.descDone": "Vous avez tous les deux confirmé le même code. La connexion est authentifiée.",
"sasv.enter": "Saisissez le code",
"sasv.match": "Correspond",
"sasv.noMatch": "Ne correspond pas",
"sasv.you": "Vous",
"sasv.peer": "Correspondant",
"sasv.waitingPeer": "En attente du correspondant…",
"sasv.codesDiffer": "Les codes ne correspondent pas",
"sasv.opening": "Ouverture du chat sécurisé",
"pwa.bannerTitle": "Installer SecureBit.chat", "pwa.bannerTitle": "Installer SecureBit.chat",
"pwa.bannerDesc": "Profitez de l'expérience d'une application native, avec une sécurité renforcée", "pwa.bannerDesc": "Profitez de l'expérience d'une application native, avec une sécurité renforcée",
"pwa.install": "Installer", "pwa.install": "Installer",
@@ -348,9 +353,8 @@
"action.scan": "Scanner", "action.scan": "Scanner",
"action.downloadDesktop": "Télécharger l'application", "action.downloadDesktop": "Télécharger l'application",
"action.advancedSettings": "Réglages avancés", "action.advancedSettings": "Réglages avancés",
"cred.offerTag": "offre · ou copiez le texte",
"cred.answerTag": "réponse · ou copiez le texte",
"cred.reveal": "Cliquez pour afficher - gardez ce code privé", "cred.reveal": "Cliquez pour afficher - gardez ce code privé",
"cred.orCopy": "Ou copiez le code",
"qr.title": "Scanner le QR", "qr.title": "Scanner le QR",
"qr.subtitle": "Visez le QR de votre correspondant", "qr.subtitle": "Visez le QR de votre correspondant",
"qr.scanning": "Lecture…", "qr.scanning": "Lecture…",
+16 -12
View File
@@ -125,9 +125,10 @@
"intro.createDesc": "המכשיר שלכם יוצר את המפתחות והזמנה חד־פעמית. שום דבר לא נוגע בשרת.", "intro.createDesc": "המכשיר שלכם יוצר את המפתחות והזמנה חד־פעמית. שום דבר לא נוגע בשרת.",
"intro.createCta": "יצירת מפתחות והזמנה", "intro.createCta": "יצירת מפתחות והזמנה",
"intro.joinTitle": "הצטרפו לערוץ", "intro.joinTitle": "הצטרפו לערוץ",
"intro.joinDesc": "סרקו את קוד ה-QR של הצד השני במצלמה, או הדביקו את קוד ההזמנה.",
"intro.scanTitle": "סרקו QR עם המצלמה", "intro.scanTitle": "סרקו QR עם המצלמה",
"intro.scanSubtitle": "הכי מהיר - כוונו למסך של העמית", "intro.scanSubtitle": "הכי מהיר - כוונו למסך של העמית",
"intro.orPasteCode": "או הדביקו קוד", "intro.orPasteCode": "או הדביקו את הקוד",
"intro.pastePlaceholder": "הדביקו כאן את קוד ההזמנה…", "intro.pastePlaceholder": "הדביקו כאן את קוד ההזמנה…",
"intro.connect": "התחברו", "intro.connect": "התחברו",
"intro.connecting": "מעבד…", "intro.connecting": "מעבד…",
@@ -303,22 +304,26 @@
"handshake.establish": "יצירת חיבור", "handshake.establish": "יצירת חיבור",
"handshake.thenReceive": "ואז קבלו את התשובה שהעמית שולח בחזרה", "handshake.thenReceive": "ואז קבלו את התשובה שהעמית שולח בחזרה",
"handshake.pasteAnswerPlaceholder": "הדביקו את קוד התשובה של העמית…", "handshake.pasteAnswerPlaceholder": "הדביקו את קוד התשובה של העמית…",
"handshake.answerSentNote": "שלחו את התשובה ליוצר ואז המתינו - הצ'אט נפתח ברגע שהם מתחברים.", "handshake.answerSentNote": "ממתינים שהיוצר יתחבר. הצ'אט ייפתח אוטומטית.",
"handshake.qrHint": "השאירו פתוח עד שהעמית יקלוט את הקוד.", "handshake.qrHint": "השאירו פתוח עד שהעמית יקלוט את הקוד.",
"handshake.qrHintFrames": "לחיצת היד מפוצלת ל-{frames} פריימים - השאירו פתוח עד שהעמית יקלוט את כולם.", "handshake.qrHintFrames": "לחיצת היד מפוצלת ל-{frames} פריימים - השאירו פתוח עד שהעמית יקלוט את כולם.",
"verify.title": "אימות אבטחה",
"verify.desc": "השוו את קוד הבטיחות הזה עם העמית בערוץ נפרד (שיחה קולית / פנים אל פנים), ואז הקלידו אותו כדי לפתוח את הצ'אט.",
"verify.enterLabel": "הזינו את הקוד המאומת",
"verify.placeholder": "הקלידו כאן את הקוד", "verify.placeholder": "הקלידו כאן את הקוד",
"verify.confirm": "אשרו את הקוד", "verify.confirm": "אשרו את הקוד",
"verify.confirmed": "אושר", "verify.confirmed": "אושר",
"verify.mismatch": "לא תואמים",
"verify.yours": "האישור שלכם",
"verify.peer": "אישור העמית",
"verify.pending": "ממתין", "verify.pending": "ממתין",
"verify.waiting": "ממתין לקוד…", "verify.waiting": "ממתין לקוד…",
"verify.verified": "הערוץ אומת", "sasv.title": "אמתו את הצד השני",
"verify.bothConfirmed": "שני הצדדים אישרו. פותח את הצ'אט המאובטח…", "sasv.titleDone": "הצד השני אומת",
"sasv.desc": "הקריאו את הקוד בקול בשיחה או פנים אל פנים. המשיכו רק אם הצד השני רואה את אותן ספרות.",
"sasv.descDone": "שניכם אישרתם את אותו קוד. החיבור מאומת.",
"sasv.enter": "הזינו את הקוד",
"sasv.match": "תואם",
"sasv.noMatch": "לא תואם",
"sasv.you": "אתם",
"sasv.peer": "הצד השני",
"sasv.waitingPeer": "ממתינים לצד השני…",
"sasv.codesDiffer": "הקודים לא תואמים",
"sasv.opening": "פותחים צ'אט מאובטח",
"pwa.bannerTitle": "התקינו את SecureBit.chat", "pwa.bannerTitle": "התקינו את SecureBit.chat",
"pwa.bannerDesc": "קבלו חוויית אפליקציה מקורית עם אבטחה משופרת", "pwa.bannerDesc": "קבלו חוויית אפליקציה מקורית עם אבטחה משופרת",
"pwa.install": "התקנה", "pwa.install": "התקנה",
@@ -353,9 +358,8 @@
"action.scan": "סריקה", "action.scan": "סריקה",
"action.downloadDesktop": "הורידו את אפליקציית שולחן העבודה", "action.downloadDesktop": "הורידו את אפליקציית שולחן העבודה",
"action.advancedSettings": "הגדרות מתקדמות", "action.advancedSettings": "הגדרות מתקדמות",
"cred.offerTag": "הצעה · או העתיקו טקסט",
"cred.answerTag": "תשובה · או העתיקו טקסט",
"cred.reveal": "לחצו לחשיפה - שמרו את הקוד הזה פרטי", "cred.reveal": "לחצו לחשיפה - שמרו את הקוד הזה פרטי",
"cred.orCopy": "או העתיקו את הקוד",
"qr.title": "סריקת קוד QR", "qr.title": "סריקת קוד QR",
"qr.subtitle": "כוונו את המצלמה ל-QR שלהם", "qr.subtitle": "כוונו את המצלמה ל-QR שלהם",
"qr.scanning": "סורק…", "qr.scanning": "סורק…",
+16 -12
View File
@@ -120,9 +120,10 @@
"intro.createDesc": "आपका उपकरण कुंजियाँ और एक बार काम आने वाला निमंत्रण बनाता है। इनमें से कुछ भी किसी सर्वर तक नहीं जाता।", "intro.createDesc": "आपका उपकरण कुंजियाँ और एक बार काम आने वाला निमंत्रण बनाता है। इनमें से कुछ भी किसी सर्वर तक नहीं जाता।",
"intro.createCta": "कुंजियाँ और निमंत्रण बनाएँ", "intro.createCta": "कुंजियाँ और निमंत्रण बनाएँ",
"intro.joinTitle": "चैनल में शामिल हों", "intro.joinTitle": "चैनल में शामिल हों",
"intro.joinDesc": "कैमरे से अपने साथी का QR स्कैन करें, या उनका आमंत्रण कोड पेस्ट करें।",
"intro.scanTitle": "कैमरे से QR स्कैन करें", "intro.scanTitle": "कैमरे से QR स्कैन करें",
"intro.scanSubtitle": "सबसे तेज़ - सामने वाले की स्क्रीन पर कैमरा रखें", "intro.scanSubtitle": "सबसे तेज़ - सामने वाले की स्क्रीन पर कैमरा रखें",
"intro.orPasteCode": "या कोड चिपकाएँ", "intro.orPasteCode": "या कोड पेस्ट करें",
"intro.pastePlaceholder": "निमंत्रण कोड यहाँ चिपकाएँ…", "intro.pastePlaceholder": "निमंत्रण कोड यहाँ चिपकाएँ…",
"intro.connect": "जोड़ें", "intro.connect": "जोड़ें",
"intro.connecting": "प्रक्रिया जारी…", "intro.connecting": "प्रक्रिया जारी…",
@@ -298,22 +299,26 @@
"handshake.establish": "जुड़ाव बनाएँ", "handshake.establish": "जुड़ाव बनाएँ",
"handshake.thenReceive": "फिर सामने वाले का भेजा उत्तर लीजिए", "handshake.thenReceive": "फिर सामने वाले का भेजा उत्तर लीजिए",
"handshake.pasteAnswerPlaceholder": "सामने वाले का उत्तर कोड चिपकाएँ…", "handshake.pasteAnswerPlaceholder": "सामने वाले का उत्तर कोड चिपकाएँ…",
"handshake.answerSentNote": "यह उत्तर चैनल बनाने वाले को भेजिए और रुकिए - जैसे ही वे जुड़ेंगे, चैट खुल जाएगी।", "handshake.answerSentNote": "निर्माता के जुड़ने की प्रतीक्षा है। चैट अपने-आप खुल जाएगी।",
"handshake.qrHint": "जब तक सामने वाला कोड पढ़ न ले, इसे खुला रखिए।", "handshake.qrHint": "जब तक सामने वाला कोड पढ़ न ले, इसे खुला रखिए।",
"handshake.qrHintFrames": "हाथ मिलाना {frames} फ़्रेमों में बँटा है - जब तक सामने वाला सब पढ़ न ले, इसे खुला रखिए।", "handshake.qrHintFrames": "हाथ मिलाना {frames} फ़्रेमों में बँटा है - जब तक सामने वाला सब पढ़ न ले, इसे खुला रखिए।",
"verify.title": "सुरक्षा सत्यापन",
"verify.desc": "इस सुरक्षा कोड को किसी दूसरे रास्ते से (आवाज़ पर या आमने-सामने) मिलाइए, फिर उसे लिखकर चैट खोलिए।",
"verify.enterLabel": "मिलाया हुआ कोड लिखें",
"verify.placeholder": "कोड यहाँ लिखें", "verify.placeholder": "कोड यहाँ लिखें",
"verify.confirm": "कोड पक्का करें", "verify.confirm": "कोड पक्का करें",
"verify.confirmed": "पक्का हुआ", "verify.confirmed": "पक्का हुआ",
"verify.mismatch": "मेल नहीं खाता",
"verify.yours": "आपकी पुष्टि",
"verify.peer": "सामने वाले की पुष्टि",
"verify.pending": "प्रतीक्षा में", "verify.pending": "प्रतीक्षा में",
"verify.waiting": "कोड की प्रतीक्षा…", "verify.waiting": "कोड की प्रतीक्षा…",
"verify.verified": "चैनल सत्यापित", "sasv.title": "अपने साथी की पुष्टि करें",
"verify.bothConfirmed": "दोनों पक्षों ने पुष्टि कर दी। सुरक्षित चैट खुल रही है…", "sasv.titleDone": "साथी की पुष्टि हो गई",
"sasv.desc": "कॉल पर या आमने-सामने कोड ज़ोर से पढ़ें। तभी आगे बढ़ें जब आपके साथी को वही अंक दिखें।",
"sasv.descDone": "आप दोनों ने एक ही कोड की पुष्टि की। कनेक्शन प्रमाणित है।",
"sasv.enter": "कोड दर्ज करें",
"sasv.match": "मेल खाता है",
"sasv.noMatch": "मेल नहीं खाता",
"sasv.you": "आप",
"sasv.peer": "साथी",
"sasv.waitingPeer": "साथी की प्रतीक्षा…",
"sasv.codesDiffer": "कोड मेल नहीं खाते",
"sasv.opening": "सुरक्षित चैट खुल रही है",
"pwa.bannerTitle": "SecureBit.chat इंस्टॉल करें", "pwa.bannerTitle": "SecureBit.chat इंस्टॉल करें",
"pwa.bannerDesc": "बेहतर सुरक्षा के साथ मूल ऐप जैसा अनुभव पाइए", "pwa.bannerDesc": "बेहतर सुरक्षा के साथ मूल ऐप जैसा अनुभव पाइए",
"pwa.install": "इंस्टॉल करें", "pwa.install": "इंस्टॉल करें",
@@ -348,9 +353,8 @@
"action.scan": "स्कैन करें", "action.scan": "स्कैन करें",
"action.downloadDesktop": "डेस्कटॉप ऐप डाउनलोड करें", "action.downloadDesktop": "डेस्कटॉप ऐप डाउनलोड करें",
"action.advancedSettings": "उन्नत सेटिंग्स", "action.advancedSettings": "उन्नत सेटिंग्स",
"cred.offerTag": "निमंत्रण · या पाठ कॉपी करें",
"cred.answerTag": "उत्तर · या पाठ कॉपी करें",
"cred.reveal": "देखने के लिए दबाएँ - यह कोड किसी को न बताएँ", "cred.reveal": "देखने के लिए दबाएँ - यह कोड किसी को न बताएँ",
"cred.orCopy": "या कोड कॉपी करें",
"qr.title": "QR कोड स्कैन करें", "qr.title": "QR कोड स्कैन करें",
"qr.subtitle": "कैमरा सामने वाले के QR पर रखें", "qr.subtitle": "कैमरा सामने वाले के QR पर रखें",
"qr.scanning": "पढ़ा जा रहा है…", "qr.scanning": "पढ़ा जा रहा है…",
+15 -11
View File
@@ -120,6 +120,7 @@
"intro.createDesc": "여러분의 기기가 키와 일회용 초대장을 만듭니다. 그 어느 것도 서버를 거치지 않습니다.", "intro.createDesc": "여러분의 기기가 키와 일회용 초대장을 만듭니다. 그 어느 것도 서버를 거치지 않습니다.",
"intro.createCta": "키와 초대장 만들기", "intro.createCta": "키와 초대장 만들기",
"intro.joinTitle": "채널 참여하기", "intro.joinTitle": "채널 참여하기",
"intro.joinDesc": "카메라로 상대방의 QR을 스캔하거나 초대 코드를 붙여넣으세요.",
"intro.scanTitle": "카메라로 QR 스캔", "intro.scanTitle": "카메라로 QR 스캔",
"intro.scanSubtitle": "가장 빠릅니다 - 상대의 화면을 비추세요", "intro.scanSubtitle": "가장 빠릅니다 - 상대의 화면을 비추세요",
"intro.orPasteCode": "또는 코드 붙여넣기", "intro.orPasteCode": "또는 코드 붙여넣기",
@@ -298,22 +299,26 @@
"handshake.establish": "연결 맺기", "handshake.establish": "연결 맺기",
"handshake.thenReceive": "그다음 상대가 보내는 응답을 받으세요", "handshake.thenReceive": "그다음 상대가 보내는 응답을 받으세요",
"handshake.pasteAnswerPlaceholder": "상대의 응답 코드를 붙여넣으세요…", "handshake.pasteAnswerPlaceholder": "상대의 응답 코드를 붙여넣으세요…",
"handshake.answerSentNote": "이 응답을 만든 사람에게 보내고 기다리세요 - 상대가 연결되면 대화가 열립니다.", "handshake.answerSentNote": "생성자의 연결을 기다리는 중입니다. 채팅이 자동으로 열립니다.",
"handshake.qrHint": "상대가 코드를 다 읽을 때까지 이 화면을 열어 두세요.", "handshake.qrHint": "상대가 코드를 다 읽을 때까지 이 화면을 열어 두세요.",
"handshake.qrHintFrames": "악수가 {frames}개 화면으로 나뉘어 있습니다 - 상대가 전부 읽을 때까지 열어 두세요.", "handshake.qrHintFrames": "악수가 {frames}개 화면으로 나뉘어 있습니다 - 상대가 전부 읽을 때까지 열어 두세요.",
"verify.title": "보안 확인",
"verify.desc": "다른 경로(통화나 대면)로 상대와 이 안전 코드를 대조한 뒤, 입력해서 대화를 여세요.",
"verify.enterLabel": "대조한 코드를 입력하세요",
"verify.placeholder": "여기에 코드 입력", "verify.placeholder": "여기에 코드 입력",
"verify.confirm": "코드 확인", "verify.confirm": "코드 확인",
"verify.confirmed": "확인됨", "verify.confirmed": "확인됨",
"verify.mismatch": "일치하지 않음",
"verify.yours": "내 확인",
"verify.peer": "상대의 확인",
"verify.pending": "대기 중", "verify.pending": "대기 중",
"verify.waiting": "코드 대기 중…", "verify.waiting": "코드 대기 중…",
"verify.verified": "채널 확인 완료", "sasv.title": "상대방 확인",
"verify.bothConfirmed": "양쪽 모두 확인했습니다. 안전한 대화를 여는 중…", "sasv.titleDone": "상대방 확인 완료",
"sasv.desc": "통화나 대면으로 코드를 소리 내어 읽어 주세요. 상대방이 같은 숫자를 볼 때만 계속하세요.",
"sasv.descDone": "두 사람 모두 같은 코드를 확인했습니다. 연결이 인증되었습니다.",
"sasv.enter": "코드 입력",
"sasv.match": "일치",
"sasv.noMatch": "일치하지 않음",
"sasv.you": "나",
"sasv.peer": "상대방",
"sasv.waitingPeer": "상대방을 기다리는 중…",
"sasv.codesDiffer": "코드가 일치하지 않음",
"sasv.opening": "보안 채팅을 여는 중",
"pwa.bannerTitle": "SecureBit.chat 설치", "pwa.bannerTitle": "SecureBit.chat 설치",
"pwa.bannerDesc": "보안이 강화된 네이티브 앱 경험을 누리세요", "pwa.bannerDesc": "보안이 강화된 네이티브 앱 경험을 누리세요",
"pwa.install": "설치", "pwa.install": "설치",
@@ -348,9 +353,8 @@
"action.scan": "스캔", "action.scan": "스캔",
"action.downloadDesktop": "데스크톱 앱 내려받기", "action.downloadDesktop": "데스크톱 앱 내려받기",
"action.advancedSettings": "고급 설정", "action.advancedSettings": "고급 설정",
"cred.offerTag": "초대 · 또는 텍스트 복사",
"cred.answerTag": "응답 · 또는 텍스트 복사",
"cred.reveal": "눌러서 보기 - 이 코드는 남에게 알리지 마세요", "cred.reveal": "눌러서 보기 - 이 코드는 남에게 알리지 마세요",
"cred.orCopy": "또는 코드 복사",
"qr.title": "QR 코드 스캔", "qr.title": "QR 코드 스캔",
"qr.subtitle": "상대의 QR에 카메라를 맞추세요", "qr.subtitle": "상대의 QR에 카메라를 맞추세요",
"qr.scanning": "읽는 중…", "qr.scanning": "읽는 중…",
+16 -12
View File
@@ -125,9 +125,10 @@
"intro.createDesc": "Ваше устройство создаёт ключи и одноразовое приглашение. Ничто из этого не попадает на сервер.", "intro.createDesc": "Ваше устройство создаёт ключи и одноразовое приглашение. Ничто из этого не попадает на сервер.",
"intro.createCta": "Создать ключи и приглашение", "intro.createCta": "Создать ключи и приглашение",
"intro.joinTitle": "Присоединиться к каналу", "intro.joinTitle": "Присоединиться к каналу",
"intro.joinDesc": "Отсканируйте QR собеседника камерой или вставьте его код приглашения.",
"intro.scanTitle": "Сканировать QR камерой", "intro.scanTitle": "Сканировать QR камерой",
"intro.scanSubtitle": "Быстрее всего - наведите на экран собеседника", "intro.scanSubtitle": "Быстрее всего - наведите на экран собеседника",
"intro.orPasteCode": "или вставьте код", "intro.orPasteCode": "Или вставьте код",
"intro.pastePlaceholder": "Вставьте код приглашения сюда…", "intro.pastePlaceholder": "Вставьте код приглашения сюда…",
"intro.connect": "Соединиться", "intro.connect": "Соединиться",
"intro.connecting": "Обработка…", "intro.connecting": "Обработка…",
@@ -303,22 +304,26 @@
"handshake.establish": "Установить соединение", "handshake.establish": "Установить соединение",
"handshake.thenReceive": "Затем получите ответ, который пришлёт собеседник", "handshake.thenReceive": "Затем получите ответ, который пришлёт собеседник",
"handshake.pasteAnswerPlaceholder": "Вставьте код ответа собеседника…", "handshake.pasteAnswerPlaceholder": "Вставьте код ответа собеседника…",
"handshake.answerSentNote": "Отправьте этот ответ создателю и подождите - чат откроется, как только он подключится.", "handshake.answerSentNote": "Ждём, пока создатель подключится. Чат откроется автоматически.",
"handshake.qrHint": "Держите это открытым, пока собеседник не считает код.", "handshake.qrHint": "Держите это открытым, пока собеседник не считает код.",
"handshake.qrHintFrames": "Рукопожатие разбито на {frames} кадров - держите это открытым, пока собеседник не считает все.", "handshake.qrHintFrames": "Рукопожатие разбито на {frames} кадров - держите это открытым, пока собеседник не считает все.",
"verify.title": "Проверка безопасности",
"verify.desc": "Сверьте этот код безопасности с собеседником по другому каналу (голосом или лично), затем введите его, чтобы открыть чат.",
"verify.enterLabel": "Введите сверенный код",
"verify.placeholder": "Введите код здесь", "verify.placeholder": "Введите код здесь",
"verify.confirm": "Подтвердить код", "verify.confirm": "Подтвердить код",
"verify.confirmed": "Подтверждено", "verify.confirmed": "Подтверждено",
"verify.mismatch": "Не совпадают",
"verify.yours": "Ваше подтверждение",
"verify.peer": "Подтверждение собеседника",
"verify.pending": "Ожидается", "verify.pending": "Ожидается",
"verify.waiting": "Ожидание кода…", "verify.waiting": "Ожидание кода…",
"verify.verified": "Канал проверен", "sasv.title": "Проверьте собеседника",
"verify.bothConfirmed": "Обе стороны подтвердили. Открываем защищённый чат…", "sasv.titleDone": "Собеседник проверен",
"sasv.desc": "Прочитайте код вслух по звонку или при встрече. Продолжайте, только если собеседник видит те же цифры.",
"sasv.descDone": "Вы оба подтвердили один и тот же код. Соединение подлинное.",
"sasv.enter": "Введите код",
"sasv.match": "Совпадает",
"sasv.noMatch": "Не совпадает",
"sasv.you": "Вы",
"sasv.peer": "Собеседник",
"sasv.waitingPeer": "Ждём собеседника…",
"sasv.codesDiffer": "Коды не совпадают",
"sasv.opening": "Открываем защищённый чат",
"pwa.bannerTitle": "Установите SecureBit.chat", "pwa.bannerTitle": "Установите SecureBit.chat",
"pwa.bannerDesc": "Опыт полноценного приложения с усиленной защитой", "pwa.bannerDesc": "Опыт полноценного приложения с усиленной защитой",
"pwa.install": "Установить", "pwa.install": "Установить",
@@ -353,9 +358,8 @@
"action.scan": "Сканировать", "action.scan": "Сканировать",
"action.downloadDesktop": "Скачать приложение", "action.downloadDesktop": "Скачать приложение",
"action.advancedSettings": "Дополнительные настройки", "action.advancedSettings": "Дополнительные настройки",
"cred.offerTag": "предложение · или скопируйте текст",
"cred.answerTag": "ответ · или скопируйте текст",
"cred.reveal": "Нажмите, чтобы показать - держите этот код в тайне", "cred.reveal": "Нажмите, чтобы показать - держите этот код в тайне",
"cred.orCopy": "Или скопируйте код",
"qr.title": "Сканировать QR-код", "qr.title": "Сканировать QR-код",
"qr.subtitle": "Наведите камеру на их QR-код", "qr.subtitle": "Наведите камеру на их QR-код",
"qr.scanning": "Сканирование…", "qr.scanning": "Сканирование…",
+16 -12
View File
@@ -125,9 +125,10 @@
"intro.createDesc": "Ваш пристрій створює ключі та одноразове запрошення. Ніщо з цього не потрапляє на сервер.", "intro.createDesc": "Ваш пристрій створює ключі та одноразове запрошення. Ніщо з цього не потрапляє на сервер.",
"intro.createCta": "Створити ключі та запрошення", "intro.createCta": "Створити ключі та запрошення",
"intro.joinTitle": "Приєднатися до каналу", "intro.joinTitle": "Приєднатися до каналу",
"intro.joinDesc": "Відскануйте QR співрозмовника камерою або вставте його код запрошення.",
"intro.scanTitle": "Сканувати QR камерою", "intro.scanTitle": "Сканувати QR камерою",
"intro.scanSubtitle": "Найшвидше - наведіть на екран співрозмовника", "intro.scanSubtitle": "Найшвидше - наведіть на екран співрозмовника",
"intro.orPasteCode": "або вставте код", "intro.orPasteCode": "Або вставте код",
"intro.pastePlaceholder": "Вставте код запрошення сюди…", "intro.pastePlaceholder": "Вставте код запрошення сюди…",
"intro.connect": "З'єднатися", "intro.connect": "З'єднатися",
"intro.connecting": "Обробка…", "intro.connecting": "Обробка…",
@@ -303,22 +304,26 @@
"handshake.establish": "Встановити з'єднання", "handshake.establish": "Встановити з'єднання",
"handshake.thenReceive": "Потім отримайте відповідь, яку надішле співрозмовник", "handshake.thenReceive": "Потім отримайте відповідь, яку надішле співрозмовник",
"handshake.pasteAnswerPlaceholder": "Вставте код відповіді співрозмовника…", "handshake.pasteAnswerPlaceholder": "Вставте код відповіді співрозмовника…",
"handshake.answerSentNote": "Надішліть цю відповідь створювачу й зачекайте - чат відкриється, щойно він приєднається.", "handshake.answerSentNote": "Чекаємо, поки творець під'єднається. Чат відкриється автоматично.",
"handshake.qrHint": "Тримайте це відкритим, доки співрозмовник не зчитає код.", "handshake.qrHint": "Тримайте це відкритим, доки співрозмовник не зчитає код.",
"handshake.qrHintFrames": "Рукостискання розбите на {frames} кадрів - тримайте це відкритим, доки співрозмовник не зчитає всі.", "handshake.qrHintFrames": "Рукостискання розбите на {frames} кадрів - тримайте це відкритим, доки співрозмовник не зчитає всі.",
"verify.title": "Перевірка безпеки",
"verify.desc": "Звірте цей код безпеки зі співрозмовником іншим каналом (голосом або особисто), потім введіть його, щоб відкрити чат.",
"verify.enterLabel": "Введіть звірений код",
"verify.placeholder": "Введіть код тут", "verify.placeholder": "Введіть код тут",
"verify.confirm": "Підтвердити код", "verify.confirm": "Підтвердити код",
"verify.confirmed": "Підтверджено", "verify.confirmed": "Підтверджено",
"verify.mismatch": "Не збігаються",
"verify.yours": "Ваше підтвердження",
"verify.peer": "Підтвердження співрозмовника",
"verify.pending": "Очікується", "verify.pending": "Очікується",
"verify.waiting": "Очікування коду…", "verify.waiting": "Очікування коду…",
"verify.verified": "Канал перевірено", "sasv.title": "Перевірте співрозмовника",
"verify.bothConfirmed": "Обидві сторони підтвердили. Відкриваємо захищений чат…", "sasv.titleDone": "Співрозмовника перевірено",
"sasv.desc": "Прочитайте код уголос під час дзвінка або зустрічі. Продовжуйте, лише якщо співрозмовник бачить ті самі цифри.",
"sasv.descDone": "Ви обидва підтвердили той самий код. З'єднання справжнє.",
"sasv.enter": "Введіть код",
"sasv.match": "Збігається",
"sasv.noMatch": "Не збігається",
"sasv.you": "Ви",
"sasv.peer": "Співрозмовник",
"sasv.waitingPeer": "Чекаємо на співрозмовника…",
"sasv.codesDiffer": "Коди не збігаються",
"sasv.opening": "Відкриваємо захищений чат",
"pwa.bannerTitle": "Встановіть SecureBit.chat", "pwa.bannerTitle": "Встановіть SecureBit.chat",
"pwa.bannerDesc": "Досвід власного застосунку з посиленим захистом", "pwa.bannerDesc": "Досвід власного застосунку з посиленим захистом",
"pwa.install": "Встановити", "pwa.install": "Встановити",
@@ -353,9 +358,8 @@
"action.scan": "Сканувати", "action.scan": "Сканувати",
"action.downloadDesktop": "Завантажити застосунок", "action.downloadDesktop": "Завантажити застосунок",
"action.advancedSettings": "Додаткові налаштування", "action.advancedSettings": "Додаткові налаштування",
"cred.offerTag": "пропозиція · або скопіюйте текст",
"cred.answerTag": "відповідь · або скопіюйте текст",
"cred.reveal": "Натисніть, щоб показати - тримайте цей код у таємниці", "cred.reveal": "Натисніть, щоб показати - тримайте цей код у таємниці",
"cred.orCopy": "Або скопіюйте код",
"qr.title": "Сканувати QR-код", "qr.title": "Сканувати QR-код",
"qr.subtitle": "Наведіть камеру на їхній QR-код", "qr.subtitle": "Наведіть камеру на їхній QR-код",
"qr.scanning": "Сканування…", "qr.scanning": "Сканування…",
+15 -11
View File
@@ -125,6 +125,7 @@
"intro.createDesc": "آپ کا آلہ کلیدیں اور ایک بار کی دعوت بناتا ہے۔ کچھ بھی سرور کو نہیں چھوتا۔", "intro.createDesc": "آپ کا آلہ کلیدیں اور ایک بار کی دعوت بناتا ہے۔ کچھ بھی سرور کو نہیں چھوتا۔",
"intro.createCta": "کلیدیں اور دعوت بنائیں", "intro.createCta": "کلیدیں اور دعوت بنائیں",
"intro.joinTitle": "چینل میں شامل ہوں", "intro.joinTitle": "چینل میں شامل ہوں",
"intro.joinDesc": "کیمرے سے اپنے ساتھی کا QR اسکین کریں، یا ان کا دعوتی کوڈ چسپاں کریں۔",
"intro.scanTitle": "کیمرے سے QR پڑھیں", "intro.scanTitle": "کیمرے سے QR پڑھیں",
"intro.scanSubtitle": "سب سے تیز - ساتھی کی سکرین کی طرف کریں", "intro.scanSubtitle": "سب سے تیز - ساتھی کی سکرین کی طرف کریں",
"intro.orPasteCode": "یا کوڈ چسپاں کریں", "intro.orPasteCode": "یا کوڈ چسپاں کریں",
@@ -303,22 +304,26 @@
"handshake.establish": "اتصال قائم کریں", "handshake.establish": "اتصال قائم کریں",
"handshake.thenReceive": "پھر وہ جواب وصول کریں جو آپ کا ساتھی واپس بھیجتا ہے", "handshake.thenReceive": "پھر وہ جواب وصول کریں جو آپ کا ساتھی واپس بھیجتا ہے",
"handshake.pasteAnswerPlaceholder": "ساتھی کا جوابی کوڈ چسپاں کریں…", "handshake.pasteAnswerPlaceholder": "ساتھی کا جوابی کوڈ چسپاں کریں…",
"handshake.answerSentNote": "یہ جواب بنانے والے کو بھیجیں، پھر انتظار کریں - ان کے جڑتے ہی گفتگو کھل جائے گی۔", "handshake.answerSentNote": "تخلیق کار کے جڑنے کا انتظار ہے۔ چیٹ خود بخود کھل جائے گی۔",
"handshake.qrHint": "یہ اس وقت تک کھلا رکھیں جب تک ساتھی کوڈ نہ پکڑ لے۔", "handshake.qrHint": "یہ اس وقت تک کھلا رکھیں جب تک ساتھی کوڈ نہ پکڑ لے۔",
"handshake.qrHintFrames": "مصافحہ {frames} فریموں میں بٹا ہے - یہ اس وقت تک کھلا رکھیں جب تک ساتھی سب نہ پکڑ لے۔", "handshake.qrHintFrames": "مصافحہ {frames} فریموں میں بٹا ہے - یہ اس وقت تک کھلا رکھیں جب تک ساتھی سب نہ پکڑ لے۔",
"verify.title": "حفاظتی تصدیق",
"verify.desc": "یہ حفاظتی کوڈ کسی الگ ذریعے (آواز / بالمشافہ) سے اپنے ساتھی کے ساتھ ملائیں، پھر گفتگو کھولنے کے لیے لکھیں۔",
"verify.enterLabel": "تصدیق شدہ کوڈ درج کریں",
"verify.placeholder": "کوڈ یہاں لکھیں", "verify.placeholder": "کوڈ یہاں لکھیں",
"verify.confirm": "کوڈ کی تصدیق", "verify.confirm": "کوڈ کی تصدیق",
"verify.confirmed": "تصدیق ہو گئی", "verify.confirmed": "تصدیق ہو گئی",
"verify.mismatch": "مطابقت نہیں",
"verify.yours": "آپ کی تصدیق",
"verify.peer": "ساتھی کی تصدیق",
"verify.pending": "زیرِ التوا", "verify.pending": "زیرِ التوا",
"verify.waiting": "کوڈ کا انتظار…", "verify.waiting": "کوڈ کا انتظار…",
"verify.verified": "چینل تصدیق شدہ", "sasv.title": "اپنے ساتھی کی تصدیق کریں",
"verify.bothConfirmed": "دونوں فریقوں نے تصدیق کی۔ محفوظ گفتگو کھولی جا رہی ہے…", "sasv.titleDone": "ساتھی کی تصدیق ہو گئی",
"sasv.desc": "کال پر یا آمنے سامنے کوڈ بلند آواز سے پڑھیں۔ صرف تب آگے بڑھیں جب آپ کے ساتھی کو وہی ہندسے نظر آئیں۔",
"sasv.descDone": "آپ دونوں نے ایک ہی کوڈ کی تصدیق کی۔ کنکشن مستند ہے۔",
"sasv.enter": "کوڈ درج کریں",
"sasv.match": "مماثل ہے",
"sasv.noMatch": "مماثل نہیں",
"sasv.you": "آپ",
"sasv.peer": "ساتھی",
"sasv.waitingPeer": "ساتھی کا انتظار…",
"sasv.codesDiffer": "کوڈ مماثل نہیں",
"sasv.opening": "محفوظ چیٹ کھل رہی ہے",
"pwa.bannerTitle": "‏SecureBit.chat نصب کریں", "pwa.bannerTitle": "‏SecureBit.chat نصب کریں",
"pwa.bannerDesc": "بہتر سلامتی کے ساتھ مقامی ایپ کا تجربہ حاصل کریں", "pwa.bannerDesc": "بہتر سلامتی کے ساتھ مقامی ایپ کا تجربہ حاصل کریں",
"pwa.install": "نصب", "pwa.install": "نصب",
@@ -353,9 +358,8 @@
"action.scan": "پڑھیں", "action.scan": "پڑھیں",
"action.downloadDesktop": "ڈیسک ٹاپ ایپ ڈاؤن لوڈ کریں", "action.downloadDesktop": "ڈیسک ٹاپ ایپ ڈاؤن لوڈ کریں",
"action.advancedSettings": "اعلیٰ ترتیبات", "action.advancedSettings": "اعلیٰ ترتیبات",
"cred.offerTag": "پیشکش · یا متن نقل کریں",
"cred.answerTag": "جواب · یا متن نقل کریں",
"cred.reveal": "دکھانے کے لیے دبائیں - یہ کوڈ نجی رکھیں", "cred.reveal": "دکھانے کے لیے دبائیں - یہ کوڈ نجی رکھیں",
"cred.orCopy": "یا کوڈ نقل کریں",
"qr.title": "‏QR کوڈ پڑھیں", "qr.title": "‏QR کوڈ پڑھیں",
"qr.subtitle": "اپنا کیمرہ ان کے QR کی طرف کریں", "qr.subtitle": "اپنا کیمرہ ان کے QR کی طرف کریں",
"qr.scanning": "پڑھا جا رہا ہے…", "qr.scanning": "پڑھا جا رہا ہے…",
+15 -11
View File
@@ -120,6 +120,7 @@
"intro.createDesc": "你的设备会生成密钥和一次性邀请。这些都不会经过任何服务器。", "intro.createDesc": "你的设备会生成密钥和一次性邀请。这些都不会经过任何服务器。",
"intro.createCta": "生成密钥与邀请", "intro.createCta": "生成密钥与邀请",
"intro.joinTitle": "加入通道", "intro.joinTitle": "加入通道",
"intro.joinDesc": "用相机扫描对方的二维码,或粘贴对方的邀请代码。",
"intro.scanTitle": "用摄像头扫描二维码", "intro.scanTitle": "用摄像头扫描二维码",
"intro.scanSubtitle": "最快的方式 —— 对准对方的屏幕", "intro.scanSubtitle": "最快的方式 —— 对准对方的屏幕",
"intro.orPasteCode": "或粘贴代码", "intro.orPasteCode": "或粘贴代码",
@@ -298,22 +299,26 @@
"handshake.establish": "建立连接", "handshake.establish": "建立连接",
"handshake.thenReceive": "然后接收对方发回的应答", "handshake.thenReceive": "然后接收对方发回的应答",
"handshake.pasteAnswerPlaceholder": "粘贴对方的应答码…", "handshake.pasteAnswerPlaceholder": "粘贴对方的应答码…",
"handshake.answerSentNote": "把这段应答发给创建者,然后等待 —— 对方一连上,聊天就会打开。", "handshake.answerSentNote": "正在等待创建者连接。聊天将自动打开。",
"handshake.qrHint": "在对方读取完代码之前,请保持此界面打开。", "handshake.qrHint": "在对方读取完代码之前,请保持此界面打开。",
"handshake.qrHintFrames": "握手被拆成 {frames} 帧 —— 在对方读完全部帧之前,请保持此界面打开。", "handshake.qrHintFrames": "握手被拆成 {frames} 帧 —— 在对方读完全部帧之前,请保持此界面打开。",
"verify.title": "安全校验",
"verify.desc": "通过另一条渠道(通话或当面)与对方核对这个安全码,然后输入它以解锁聊天。",
"verify.enterLabel": "输入核对过的安全码",
"verify.placeholder": "在此输入安全码", "verify.placeholder": "在此输入安全码",
"verify.confirm": "确认安全码", "verify.confirm": "确认安全码",
"verify.confirmed": "已确认", "verify.confirmed": "已确认",
"verify.mismatch": "不一致",
"verify.yours": "你的确认",
"verify.peer": "对方的确认",
"verify.pending": "等待中", "verify.pending": "等待中",
"verify.waiting": "等待安全码…", "verify.waiting": "等待安全码…",
"verify.verified": "通道已校验", "sasv.title": "验证对方",
"verify.bothConfirmed": "双方均已确认。正在打开安全聊天…", "sasv.titleDone": "对方已验证",
"sasv.desc": "通过通话或当面大声读出代码。只有对方看到相同的数字时才继续。",
"sasv.descDone": "你们双方确认了相同的代码。连接已通过认证。",
"sasv.enter": "输入代码",
"sasv.match": "一致",
"sasv.noMatch": "不一致",
"sasv.you": "你",
"sasv.peer": "对方",
"sasv.waitingPeer": "正在等待对方…",
"sasv.codesDiffer": "代码不一致",
"sasv.opening": "正在打开安全聊天",
"pwa.bannerTitle": "安装 SecureBit.chat", "pwa.bannerTitle": "安装 SecureBit.chat",
"pwa.bannerDesc": "获得原生应用般的体验,安全性更进一步", "pwa.bannerDesc": "获得原生应用般的体验,安全性更进一步",
"pwa.install": "安装", "pwa.install": "安装",
@@ -348,9 +353,8 @@
"action.scan": "扫描", "action.scan": "扫描",
"action.downloadDesktop": "下载桌面应用", "action.downloadDesktop": "下载桌面应用",
"action.advancedSettings": "高级设置", "action.advancedSettings": "高级设置",
"cred.offerTag": "邀请 · 或复制文本",
"cred.answerTag": "应答 · 或复制文本",
"cred.reveal": "点击显示 - 请勿泄露此代码", "cred.reveal": "点击显示 - 请勿泄露此代码",
"cred.orCopy": "或复制代码",
"qr.title": "扫描二维码", "qr.title": "扫描二维码",
"qr.subtitle": "把摄像头对准对方的二维码", "qr.subtitle": "把摄像头对准对方的二维码",
"qr.scanning": "扫描中…", "qr.scanning": "扫描中…",
+7 -7
View File
@@ -1,10 +1,10 @@
{ {
"version": "1790225533889", "version": "1790540575787",
"buildVersion": "1790225533889", "buildVersion": "1790540575787",
"appVersion": "6.8.8", "appVersion": "6.8.12",
"buildTime": "2026-09-24T04:52:13.935Z", "buildTime": "2026-09-27T20:22:55.824Z",
"buildId": "1790225533889-eac3009", "buildId": "1790540575787-cd9d78d",
"gitHash": "eac3009", "gitHash": "cd9d78d",
"generated": true, "generated": true,
"generatedAt": "2026-09-24T04:52:13.936Z" "generatedAt": "2026-09-27T20:22:55.825Z"
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "securebit-chat", "name": "securebit-chat",
"version": "6.8.8", "version": "6.8.12",
"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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/ru.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
</body> </body>
</html> </html>
+23 -23
View File
@@ -17,7 +17,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -37,7 +37,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -57,7 +57,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -77,7 +77,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -97,7 +97,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -117,7 +117,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -137,7 +137,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -157,7 +157,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -177,7 +177,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -197,7 +197,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -217,7 +217,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -237,7 +237,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
@@ -257,67 +257,67 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/> <xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/> <xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>1.0</priority> <priority>1.0</priority>
</url> </url>
<url> <url>
<loc>https://securebit.chat/docs/</loc> <loc>https://securebit.chat/docs/</loc>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
<url> <url>
<loc>https://securebit.chat/docs/faq/</loc> <loc>https://securebit.chat/docs/faq/</loc>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
<url> <url>
<loc>https://securebit.chat/docs/architecture/</loc> <loc>https://securebit.chat/docs/architecture/</loc>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
<url> <url>
<loc>https://securebit.chat/docs/cryptography/</loc> <loc>https://securebit.chat/docs/cryptography/</loc>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
<url> <url>
<loc>https://securebit.chat/docs/descriptor-sbq2/</loc> <loc>https://securebit.chat/docs/descriptor-sbq2/</loc>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
<url> <url>
<loc>https://securebit.chat/docs/configuration/</loc> <loc>https://securebit.chat/docs/configuration/</loc>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
<url> <url>
<loc>https://securebit.chat/docs/calls/</loc> <loc>https://securebit.chat/docs/calls/</loc>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
<url> <url>
<loc>https://securebit.chat/docs/api/</loc> <loc>https://securebit.chat/docs/api/</loc>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
<url> <url>
<loc>https://securebit.chat/docs/contributing/</loc> <loc>https://securebit.chat/docs/contributing/</loc>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
<url> <url>
<loc>https://securebit.chat/docs/use-policy/</loc> <loc>https://securebit.chat/docs/use-policy/</loc>
<lastmod>2026-09-24</lastmod> <lastmod>2026-09-27</lastmod>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<priority>0.7</priority> <priority>0.7</priority>
</url> </url>
+192 -84
View File
@@ -1170,9 +1170,23 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
const [copied, setCopied] = React.useState(false); const [copied, setCopied] = React.useState(false);
const [sasInput, setSasInput] = React.useState(''); const [sasInput, setSasInput] = React.useState('');
const [sasError, setSasError] = React.useState(''); const [sasError, setSasError] = React.useState('');
const [sasFocused, setSasFocused] = React.useState(false);
const [sasShake, setSasShake] = React.useState(0);
const [platformsOpen, setPlatformsOpen] = React.useState(false); const [platformsOpen, setPlatformsOpen] = React.useState(false);
const [codeRevealed, setCodeRevealed] = React.useState(false); const [codeRevealed, setCodeRevealed] = React.useState(false);
const [genProgress, setGenProgress] = React.useState(0); const [genProgress, setGenProgress] = React.useState(0);
// Countdown on the invitation: the joining side refuses an offer older
// than 30 minutes (MAX_OFFER_AGE in EnhancedSecureWebRTCManager), measured
// from the offer's own timestamp.
const OFFER_TTL_MS = 30 * 60 * 1000;
const offerTs = (offerData && typeof offerData === 'object' && Number(offerData.ts || offerData.timestamp)) || null;
const [nowTick, setNowTick] = React.useState(() => Date.now());
React.useEffect(() => {
if (!showOfferStep || !offerTs) return undefined;
setNowTick(Date.now());
const id = setInterval(() => setNowTick(Date.now()), 1000);
return () => clearInterval(id);
}, [showOfferStep, offerTs]);
// Reset the typed SAS whenever a fresh verification code arrives // Reset the typed SAS whenever a fresh verification code arrives
React.useEffect(() => { setSasInput(''); setSasError(''); }, [verificationCode]); React.useEffect(() => { setSasInput(''); setSasError(''); }, [verificationCode]);
@@ -1549,72 +1563,151 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
const backButton = (key) => h('button', { key: key || 'back', className: 'sb-soft-btn', onClick: resetToSelect, style: { display: 'inline-flex', alignItems: 'center', gap: '6px', marginBottom: '14px', paddingBlock: '6px', paddingInlineStart: '8px', paddingInlineEnd: '11px', borderRadius: '8px', border: '1px solid rgba(var(--sb-ink), 0.08)', background: 'transparent', color: 'var(--sb-text-6)', fontFamily: 'inherit', fontSize: '12.5px', fontWeight: 600, cursor: 'pointer' } }, [fa('fa-chevron-left', { key: 'i' }), t('action.back')]); const backButton = (key) => h('button', { key: key || 'back', className: 'sb-soft-btn', onClick: resetToSelect, style: { display: 'inline-flex', alignItems: 'center', gap: '6px', marginBottom: '14px', paddingBlock: '6px', paddingInlineStart: '8px', paddingInlineEnd: '11px', borderRadius: '8px', border: '1px solid rgba(var(--sb-ink), 0.08)', background: 'transparent', color: 'var(--sb-text-6)', fontFamily: 'inherit', fontSize: '12.5px', fontWeight: 600, cursor: 'pointer' } }, [fa('fa-chevron-left', { key: 'i' }), t('action.back')]);
// credential code block (offer/answer text fallback + copy) // Invitation card (Share Invitation v3 design): QR preview row on top,
const credBlock = h('div', { key: 'codeblock', style: { borderRadius: '13px', border: '1px solid rgba(var(--sb-ink), 0.08)', background: 'var(--sb-surface)', overflow: 'hidden', marginBottom: '16px' } }, [ // the text code with copy + countdown below, one bordered surface.
h('div', { key: 'bar', style: { display: 'flex', alignItems: 'center', gap: '8px', padding: '9px 12px', borderBottom: '1px solid rgba(var(--sb-ink), 0.06)', background: 'rgba(0,0,0,0.2)' } }, [ const offerLeft = (isCreate && offerTs) ? Math.max(0, Math.ceil((offerTs + OFFER_TTL_MS - nowTick) / 1000)) : null;
h('span', { key: 'dot', style: { width: '7px', height: '7px', borderRadius: '50%', background: accent } }), const offerExpires = offerLeft === null ? null : `${Math.floor(offerLeft / 60)}:${String(offerLeft % 60).padStart(2, '0')}`;
h('span', { key: 'tag', style: { fontFamily: MONO, fontSize: '10.5px', fontWeight: 600, color: 'var(--sb-text-7)' } }, isCreate ? t('cred.offerTag') : t('cred.answerTag')), const credCard = h('div', { key: 'card', style: { borderRadius: '16px', border: '1px solid rgba(var(--sb-ink), 0.07)', background: 'var(--sb-surface)', overflow: 'hidden', marginBottom: isCreate ? '26px' : '16px' } }, [
h('button', { key: 'copy', onClick: copyCred, style: { marginInlineStart: 'auto', padding: '4px 9px', borderRadius: '6px', border: `1px solid ${copied ? 'rgba(var(--sb-green-rgb), 0.4)' : 'rgba(var(--sb-ink), 0.1)'}`, background: copied ? 'rgba(var(--sb-green-rgb), 0.1)' : 'rgba(var(--sb-ink), 0.04)', color: copied ? C_GREEN : 'var(--sb-text-5)', fontFamily: 'inherit', fontSize: '11px', fontWeight: 600, cursor: 'pointer', transition: 'all .14s' } }, copied ? t('action.copied') : t('action.copy')) qrCodeUrl && h('button', { key: 'showqr', className: 'sb-qr-row', onClick: () => setQrModalOpen(true), style: { width: '100%', display: 'flex', alignItems: 'center', gap: '14px', padding: '14px 16px', border: 'none', background: 'transparent', color: 'inherit', fontFamily: 'inherit', cursor: 'pointer', textAlign: 'start', transition: 'background .15s' } }, [
h('span', { key: 'ic', style: { flex: 'none', width: '48px', height: '48px', padding: '5px', borderRadius: '9px', background: '#fff' } },
h('img', { src: qrCodeUrl, alt: '', style: { width: '100%', height: '100%', objectFit: 'contain', display: 'block', imageRendering: 'pixelated' } })),
h('span', { key: 'tx', style: { flex: 1, minWidth: 0 } }, [
h('span', { key: 't', style: { display: 'block', fontSize: '15px', fontWeight: 700, color: 'var(--sb-text-1)' } }, t('qr.showTitle')),
h('span', { key: 's', style: { display: 'block', fontSize: '12.5px', color: 'var(--sb-text-7)', marginTop: '2px' } }, (qrFramesTotal || 0) > 1 ? t('qr.showSubtitleFrames', { frames: qrFramesTotal }) : t('qr.showSubtitle'))
]),
fa('fa-chevron-right', { color: 'var(--sb-text-9)' })
]), ]),
// The handshake code is sensitive — keep it blurred until the qrCodeUrl && h('div', { key: 'div', style: { height: '1px', background: 'rgba(var(--sb-ink), 0.06)' } }),
// user deliberately reveals it, underscoring that it must be h('div', { key: 'code', style: { padding: '12px 16px 16px' } }, [
// shared only over a channel they trust. h('div', { key: 'bar', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '10px', marginBottom: '10px' } }, [
h('div', { key: 'codewrap', style: { position: 'relative' } }, [ h('span', { key: 'l', style: { fontSize: '12.5px', fontWeight: 600, color: 'var(--sb-text-6)' } }, t('cred.orCopy')),
h('div', { key: 'code', className: 'sb-sc', style: { fontFamily: MONO, fontSize: '11px', lineHeight: 1.55, color: 'var(--sb-text-code)', wordBreak: 'break-all', padding: '11px 12px', maxHeight: '72px', overflowY: 'auto', filter: codeRevealed ? 'none' : 'blur(6px)', userSelect: codeRevealed ? 'text' : 'none', transition: 'filter .2s' } }, credCode), h('span', { key: 'r', style: { display: 'flex', alignItems: 'center', gap: '12px' } }, [
!codeRevealed && h('button', { key: 'reveal', onClick: () => setCodeRevealed(true), style: { position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', border: 'none', background: 'rgba(var(--sb-surface-rgb), 0.25)', color: 'var(--sb-text-4)', fontFamily: 'inherit', fontSize: '12px', fontWeight: 600, cursor: 'pointer' } }, [ offerExpires && h('span', { key: 'exp', dir: 'ltr', style: { fontFamily: MONO, fontSize: '11px', color: offerLeft < 60 ? 'var(--sb-red)' : 'var(--sb-text-9)' } }, offerExpires),
fa('fa-eye', { key: 'i', fontSize: '15px' }), h('button', { key: 'copy', className: 'sb-soft-btn', onClick: copyCred, style: { display: 'inline-flex', alignItems: 'center', gap: '6px', padding: '6px 11px', borderRadius: '8px', border: '1px solid rgba(var(--sb-ink), 0.1)', background: 'rgba(var(--sb-ink), 0.03)', color: 'var(--sb-text-2)', fontFamily: 'inherit', fontSize: '12.5px', fontWeight: 700, cursor: 'pointer' } }, [
t('cred.reveal') copied && fa('fa-check', { key: 'i', color: C_GREEN_SOLID }),
copied ? t('action.copied') : t('action.copy')
])
])
]),
// The handshake code is sensitive — keep it blurred until the
// user deliberately reveals it, underscoring that it must be
// shared only over a channel they trust.
h('div', { key: 'codewrap', onClick: () => setCodeRevealed((v) => !v), style: { position: 'relative', borderRadius: '11px', border: '1px solid rgba(var(--sb-ink), 0.05)', background: 'var(--sb-bg)', overflow: 'hidden', cursor: 'pointer' } }, [
h('div', { key: 'code', dir: 'ltr', className: 'sb-sc', style: { fontFamily: MONO, fontSize: '11.5px', lineHeight: 1.6, color: 'var(--sb-text-code)', wordBreak: 'break-all', padding: '11px 13px', maxHeight: '78px', overflowY: codeRevealed ? 'auto' : 'hidden', filter: codeRevealed ? 'none' : 'blur(5px)', userSelect: codeRevealed ? 'text' : 'none', transition: 'filter .25s' } }, credCode),
!codeRevealed && h('span', { key: 'reveal', style: { position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '0 12px', textAlign: 'center', color: 'var(--sb-text-3)', fontSize: '12.5px', fontWeight: 600 } }, [
fa('fa-eye', { key: 'i' }),
t('cred.reveal')
])
]) ])
]) ])
]); ]);
const showQrButton = qrCodeUrl && h('button', { key: 'showqr', onClick: () => setQrModalOpen(true), style: { width: '100%', display: 'flex', alignItems: 'center', gap: '13px', padding: '15px 16px', borderRadius: '14px', border: `1px solid ${isCreate ? 'rgba(var(--sb-orange-rgb), 0.3)' : 'rgba(var(--sb-green-rgb), 0.3)'}`, background: isCreate ? 'rgba(var(--sb-orange-rgb), 0.06)' : 'rgba(var(--sb-green-rgb), 0.06)', color: 'inherit', fontFamily: 'inherit', cursor: 'pointer', textAlign: 'start', marginBottom: '14px' } }, [
h('span', { key: 'ic', style: { flex: 'none', width: '42px', height: '42px', borderRadius: '12px', display: 'grid', placeItems: 'center', background: isCreate ? 'rgba(var(--sb-orange-rgb), 0.12)' : 'rgba(var(--sb-green-rgb), 0.12)', border: `1px solid ${isCreate ? 'rgba(var(--sb-orange-rgb), 0.28)' : 'rgba(var(--sb-green-rgb), 0.28)'}` } }, fa('fa-qrcode', { color: accentSolid, fontSize: '18px' })),
h('span', { key: 'tx', style: { flex: 1 } }, [
h('span', { key: 't', style: { display: 'block', fontSize: '14.5px', fontWeight: 700, color: 'var(--sb-text-1)' } }, t('qr.showTitle')),
h('span', { key: 's', style: { display: 'block', fontSize: '12.5px', color: 'var(--sb-text-7)', marginTop: '1px' } }, (qrFramesTotal || 0) > 1 ? t('qr.showSubtitleFrames', { frames: qrFramesTotal }) : t('qr.showSubtitle'))
]),
fa('fa-chevron-right', { color: 'var(--sb-text-9)' })
]);
let inner; let inner;
if (showVerification) { if (showVerification) {
// Transcribed from the Claude Design component (SAS Verification.dc.html),
// without its card: it sits straight in the setup column like the other steps.
const verified = bothVerificationsConfirmed; const verified = bothVerificationsConfirmed;
const cells = (verificationCode || '').split('').map((ch, i) => const code = (verificationCode || '').replace(/[-\s]/g, '');
h('div', { key: i, style: { flex: 1, maxWidth: '46px', aspectRatio: '0.82', display: 'grid', placeItems: 'center', borderRadius: '10px', border: '1px solid rgba(var(--sb-green-rgb), 0.25)', background: 'rgba(var(--sb-green-rgb), 0.05)', fontFamily: MONO, fontSize: '22px', fontWeight: 700, color: C_GREEN } }, ch)); const n = code.length;
const typed = sasInput.replace(/[-\s]/g, '');
const full = n > 0 && typed.length === n;
// Comparing against the code already on this screen reveals nothing; it
// only spares a typo one of the limited real attempts in onVerifyConnection.
const match = full && typed === code;
const wrong = full && !match;
const youOk = localVerificationConfirmed;
const peerOk = remoteVerificationConfirmed;
const waiting = youOk && !peerOk;
const split = Math.min(3, n);
const confirmable = canConfirm && match;
const check = (k) => h('svg', { key: k, width: 12, height: 12, viewBox: '0 0 24 24', fill: 'none', stroke: 'var(--sb-on-green-2)', strokeWidth: 3.2, strokeLinecap: 'round', strokeLinejoin: 'round' },
h('path', { d: 'M5 12.5l4.2 4.2L19 7', strokeDasharray: 24, style: { animation: 'svDraw .35s ease-out .1s both' } }));
const ring = (ok, soft) => ({
position: 'relative', width: '22px', height: '22px', flex: 'none', borderRadius: '50%', display: 'grid', placeItems: 'center', transition: 'all .3s',
border: `1.5px solid ${ok ? C_GREEN_SOLID : (soft ? 'rgba(var(--sb-green-rgb), 0.5)' : 'rgba(var(--sb-ink), 0.18)')}`,
background: ok ? C_GREEN_SOLID : 'transparent'
});
const who = (k, label) => h('span', { key: k, style: { fontSize: '13px', fontWeight: 600, color: 'var(--sb-text-4)' } }, label);
const cells = code.split('').map((_, i) => {
const v = typed[i] || '';
const active = sasFocused && !youOk && i === typed.length && !full;
let border = 'rgba(var(--sb-ink), 0.09)';
let color = 'var(--sb-text-1)';
let anim = 'none';
if (v) { border = 'rgba(var(--sb-ink), 0.2)'; anim = 'otpPop .28s cubic-bezier(.2,.8,.3,1.2) both'; }
if (active) border = 'rgba(var(--sb-orange-rgb), 0.65)';
if (wrong) { border = 'rgba(var(--sb-red-rgb), 0.6)'; color = 'var(--sb-red)'; }
if (match) anim = `otpOk .45s cubic-bezier(.2,.8,.3,1) ${i * 55}ms both`;
return h('span', { key: i, style: {
position: 'relative', flex: 1, minWidth: 0, height: '56px', display: 'grid', placeItems: 'center', borderRadius: '12px',
border: `1px solid ${border}`, background: 'var(--sb-surface)', fontFamily: MONO, fontSize: '22px', fontWeight: 600, color,
transition: 'border-color .15s, color .15s, box-shadow .15s', animation: anim,
marginInlineStart: i === split && n > split ? '10px' : 0,
boxShadow: active ? '0 0 0 3px rgba(var(--sb-orange-rgb), 0.12)' : 'none'
} }, [
v,
active && h('span', { key: 'caret', style: { position: 'absolute', left: '50%', top: '50%', width: '2px', height: '22px', margin: '-11px 0 0 -1px', borderRadius: '1px', background: C_ORANGE_SOLID, animation: 'otpCaret 1s steps(1) infinite' } })
]);
});
inner = h('div', { key: 'verify', style: { animation: 'sbUp .3s ease' } }, [ inner = h('div', { key: 'verify', style: { animation: 'sbUp .3s ease' } }, [
!verified && backButton('vback'), !verified && backButton('vback'),
h('div', { key: 'head', style: { display: 'flex', alignItems: 'center', gap: '11px', marginBottom: '8px' } }, [ h('h2', { key: 't', style: { margin: '0 0 8px', fontSize: '22px', fontWeight: 800, letterSpacing: '-0.5px', color: 'var(--sb-text-1)' } }, verified ? t('sasv.titleDone') : t('sasv.title')),
h('div', { key: 'i', style: { width: '34px', height: '34px', flex: 'none', borderRadius: '10px', display: 'grid', placeItems: 'center', background: 'rgba(var(--sb-green-rgb), 0.1)', border: '1px solid rgba(var(--sb-green-rgb), 0.25)' } }, fa('fa-shield-alt', { color: C_GREEN_SOLID })), h('p', { key: 'sub', style: { margin: '0 0 24px', fontSize: '13.5px', lineHeight: 1.55, color: 'var(--sb-text-7)', textWrap: 'pretty' } }, verified ? t('sasv.descDone') : t('sasv.desc')),
h('h2', { key: 't', style: { margin: 0, fontSize: '21px', fontWeight: 800, letterSpacing: '-0.4px', color: 'var(--sb-text-1)' } }, t('verify.title')) h('div', { key: 'code', style: { padding: '20px 0 22px', borderTop: '1px solid rgba(var(--sb-ink), 0.06)', borderBottom: '1px solid rgba(var(--sb-ink), 0.06)', marginBottom: '22px', textAlign: 'center' } },
h('div', { dir: 'ltr', style: { display: 'inline-flex', alignItems: 'center', gap: '18px', fontFamily: MONO, fontSize: '34px', fontWeight: 600, letterSpacing: '8px', lineHeight: 1, color: verified ? C_GREEN : 'var(--sb-text-1)', transition: 'color .4s' } },
n ? [h('span', { key: 'a' }, code.slice(0, split)), h('span', { key: 'b' }, code.slice(split))]
: h('span', { style: { fontSize: '14px', letterSpacing: 0, color: 'var(--sb-text-8)' } }, t('verify.waiting')))),
!verified && h('div', { key: 'form' }, [
h('div', { key: 'lbl', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '10px' } }, [
h('span', { key: 'l', style: { fontSize: '12.5px', fontWeight: 600, color: 'var(--sb-text-6)' } }, t('sasv.enter')),
h('span', { key: 'h', style: { fontFamily: MONO, fontSize: '11px', transition: 'color .2s', color: full ? (match ? C_GREEN : 'var(--sb-red)') : 'var(--sb-text-faint)' } },
full ? (match ? t('sasv.match') : t('sasv.noMatch')) : `${typed.length} / ${n || SAS_CODE_LENGTH}`)
]),
h('label', { key: 'cells', dir: 'ltr', style: { position: 'relative', display: 'flex', gap: '6px', marginBottom: sasError ? '8px' : '18px', cursor: 'text', animation: wrong ? `otpShake .4s ease ${sasShake % 2 ? '0s' : '0.001s'}` : 'none' } }, [
...cells,
h('input', { key: 'in', dir: 'ltr', value: sasInput, disabled: youOk || !n, autoFocus: true, autoComplete: 'one-time-code', spellCheck: false, type: 'text', inputMode: 'numeric', pattern: '[0-9]*', maxLength: n || SAS_CODE_LENGTH, 'aria-label': t('sasv.enter'),
onChange: (e) => {
const v = e.target.value.replace(/\D/g, '').slice(0, n || SAS_CODE_LENGTH);
if (n && v.length === n && v !== code) setSasShake((k) => k + 1);
setSasInput(v);
if (sasError) setSasError('');
},
onFocus: () => setSasFocused(true), onBlur: () => setSasFocused(false),
style: { position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: 0, border: 'none', outline: 'none', background: 'transparent', color: 'transparent', caretColor: 'transparent', fontSize: '16px' } })
]),
sasError && h('p', { key: 'err', style: { color: 'var(--sb-red)', fontSize: '12.5px', margin: '0 0 16px' } }, sasError)
]), ]),
h('p', { key: 'sub', style: { margin: '0 0 18px', fontSize: '13.5px', lineHeight: 1.55, color: 'var(--sb-text-7)' } }, t('verify.desc')), h('div', { key: 'link', style: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '20px' } }, [
h('div', { key: 'cells', dir: 'ltr', style: { display: 'flex', gap: '6px', justifyContent: 'center', marginBottom: '20px', flexWrap: 'wrap' } }, cells), h('div', { key: 'you', style: { display: 'flex', alignItems: 'center', gap: '9px', flex: 'none' } }, [
verified h('span', { key: 'r', style: ring(youOk, false) }, youOk && check('c')),
? h('div', { key: 'ok', style: { display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center', padding: '24px 16px', borderRadius: '16px', border: '1px solid rgba(var(--sb-green-rgb), 0.25)', background: 'rgba(var(--sb-green-rgb), 0.06)', animation: 'sbUp .3s ease' } }, [ who('l', t('sasv.you'))
h('div', { key: 'i', style: { width: '54px', height: '54px', borderRadius: '16px', display: 'grid', placeItems: 'center', background: 'rgba(var(--sb-green-rgb), 0.14)', border: '1px solid rgba(var(--sb-green-rgb), 0.35)', marginBottom: '14px' } }, fa('fa-check', { color: C_GREEN_SOLID, fontSize: '24px' })), ]),
h('div', { key: 't', style: { fontSize: '18px', fontWeight: 800, color: 'var(--sb-text-1)' } }, t('verify.verified')), h('div', { key: 'bar', style: { position: 'relative', flex: 1, height: '2px', borderRadius: '2px', background: 'rgba(var(--sb-ink), 0.08)', overflow: 'hidden' } }, [
h('div', { key: 's', style: { fontSize: '13.5px', color: 'var(--sb-text-7)', marginTop: '5px' } }, t('verify.bothConfirmed')) h('div', { key: 'fill', style: { position: 'absolute', insetInlineStart: 0, top: 0, bottom: 0, width: peerOk ? '100%' : (youOk ? '50%' : '0%'), background: C_GREEN_SOLID, transition: 'width .6s cubic-bezier(.4,0,.2,1)' } }),
waiting && h('div', { key: 'scan', style: { position: 'absolute', top: 0, bottom: 0, width: '30%', background: 'linear-gradient(90deg, transparent, rgba(var(--sb-green-rgb), 0.9), transparent)', animation: 'svScan 1.1s ease-in-out infinite' } })
]),
h('div', { key: 'peer', style: { display: 'flex', alignItems: 'center', gap: '9px', flex: 'none' } }, [
who('l', t('sasv.peer')),
h('span', { key: 'r', style: ring(peerOk, youOk) }, peerOk && check('c'))
]) ])
: h('div', { key: 'form' }, [ ]),
h('div', { key: 'lbl', style: { fontSize: '12.5px', fontWeight: 600, color: 'var(--sb-text-6)', marginBottom: '8px' } }, t('verify.enterLabel')), !verified
h('input', { key: 'in', dir: 'ltr', value: sasInput, onChange: (e) => { setSasInput(e.target.value.replace(/\D/g, '').slice(0, SAS_CODE_LENGTH)); if (sasError) setSasError(''); }, disabled: localVerificationConfirmed, autoFocus: true, autoComplete: 'one-time-code', spellCheck: false, type: 'text', inputMode: 'numeric', pattern: '[0-9]*', maxLength: SAS_CODE_LENGTH, placeholder: verificationCode ? t('verify.placeholder') : t('verify.waiting'), style: { width: '100%', textAlign: 'center', letterSpacing: '6px', borderRadius: '12px', border: `1px solid ${sasInput.length ? (canConfirm || localVerificationConfirmed ? 'rgba(var(--sb-green-rgb), 0.5)' : 'rgba(var(--sb-ink), 0.14)') : 'rgba(var(--sb-ink), 0.08)'}`, background: 'var(--sb-surface)', color: 'var(--sb-text-1)', fontFamily: MONO, fontSize: '20px', fontWeight: 700, padding: '14px', outline: 'none', marginBottom: sasError ? '8px' : '16px' } }), ? h('div', { key: 'btns' }, [
sasError && h('p', { key: 'err', style: { color: 'var(--sb-red)', fontSize: '12.5px', margin: '0 0 16px' } }, sasError), h('button', { key: 'ok', onClick: handleSasConfirm, disabled: !confirmable, style: { width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '14px', borderRadius: '13px', border: 'none', fontFamily: 'inherit', fontSize: '14.5px', fontWeight: 700, transition: 'background .25s, color .25s',
h('div', { key: 'status', style: { display: 'flex', flexDirection: 'column', gap: '8px', marginBottom: '16px' } }, [ background: youOk ? 'rgba(var(--sb-green-rgb), 0.12)' : (confirmable ? C_GREEN_SOLID : 'rgba(var(--sb-ink), 0.05)'),
h('div', { key: 'you', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '11px 14px', borderRadius: '11px', border: '1px solid rgba(var(--sb-ink), 0.06)', background: 'var(--sb-surface)' } }, [ color: youOk ? C_GREEN : (confirmable ? 'var(--sb-on-green-2)' : 'var(--sb-text-faint)'),
h('span', { key: 'l', style: { fontSize: '13px', color: 'var(--sb-text-4)', fontWeight: 600 } }, t('verify.yours')), cursor: confirmable ? 'pointer' : 'default' } }, youOk ? t('sasv.waitingPeer') : t('verify.confirm')),
h('span', { key: 'v', style: { display: 'inline-flex', alignItems: 'center', gap: '6px', fontSize: '12.5px', fontWeight: 600, color: localVerificationConfirmed ? C_GREEN : 'var(--sb-text-8)' } }, [fa(localVerificationConfirmed ? 'fa-check-circle' : 'fa-clock', { key: 'i' }), localVerificationConfirmed ? t('verify.confirmed') : t('verify.pending')]) h('button', { key: 'no', onClick: handleVerificationReject, className: 'sb-sas-mismatch', style: { display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', marginTop: '10px', padding: '8px', border: 'none', background: 'transparent', color: 'var(--sb-text-7)', fontFamily: 'inherit', fontSize: '13px', fontWeight: 600, cursor: 'pointer', transition: 'color .15s' },
]), onMouseEnter: (e) => { e.currentTarget.style.color = 'var(--sb-red)'; }, onMouseLeave: (e) => { e.currentTarget.style.color = 'var(--sb-text-7)'; } }, t('sasv.codesDiffer'))
h('div', { key: 'peer', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '11px 14px', borderRadius: '11px', border: '1px solid rgba(var(--sb-ink), 0.06)', background: 'var(--sb-surface)' } }, [
h('span', { key: 'l', style: { fontSize: '13px', color: 'var(--sb-text-4)', fontWeight: 600 } }, t('verify.peer')),
h('span', { key: 'v', style: { display: 'inline-flex', alignItems: 'center', gap: '6px', fontSize: '12.5px', fontWeight: 600, color: remoteVerificationConfirmed ? C_GREEN : 'var(--sb-text-8)' } }, [fa(remoteVerificationConfirmed ? 'fa-check-circle' : 'fa-clock', { key: 'i' }), remoteVerificationConfirmed ? t('verify.confirmed') : t('verify.pending')])
])
]),
h('div', { key: 'btns', style: { display: 'flex', gap: '10px' } }, [
h('button', { key: 'ok', onClick: handleSasConfirm, disabled: !canConfirm, style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '14px', borderRadius: '13px', border: 'none', background: canConfirm ? C_GREEN_SOLID : 'rgba(var(--sb-ink), 0.05)', color: canConfirm ? 'var(--sb-on-green-2)' : 'var(--sb-text-faint)', fontFamily: 'inherit', fontSize: '14.5px', fontWeight: 700, cursor: canConfirm ? 'pointer' : 'not-allowed', boxShadow: canConfirm ? '0 8px 24px rgba(var(--sb-green-rgb), 0.25)' : 'none' } }, [fa(localVerificationConfirmed ? 'fa-check-circle' : 'fa-check', { key: 'i' }), localVerificationConfirmed ? t('verify.confirmed') : t('verify.confirm')]),
h('button', { key: 'no', onClick: handleVerificationReject, style: { flex: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '7px', padding: '14px 16px', borderRadius: '13px', border: '1px solid rgba(var(--sb-red-rgb), 0.3)', background: 'transparent', color: 'var(--sb-red)', fontFamily: 'inherit', fontSize: '13.5px', fontWeight: 600, cursor: 'pointer' } }, [fa('fa-times', { key: 'i' }), t('verify.mismatch')])
])
]) ])
: h('div', { key: 'opening', style: { animation: 'svFade .35s ease .3s both' } },
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '10px', padding: '14px 0 4px', fontSize: '13.5px', fontWeight: 600, color: 'var(--sb-text-4)' } }, [
t('sasv.opening'),
h('span', { key: 'd', style: { display: 'inline-flex', gap: '4px' } }, [0, 0.15, 0.3].map((d, i) =>
h('span', { key: i, style: { width: '4px', height: '4px', borderRadius: '50%', background: C_GREEN_SOLID, animation: `svDot 1.2s ease-in-out ${d}s infinite` } })))
]))
]); ]);
} else if (isGenerating) { } else if (isGenerating) {
const genSteps = [t('handshake.step1'), t('handshake.step2'), t('handshake.step3')]; const genSteps = [t('handshake.step1'), t('handshake.step2'), t('handshake.step3')];
@@ -1643,22 +1736,24 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
} else if (isOfferCred || isAnswerCred) { } else if (isOfferCred || isAnswerCred) {
inner = h('div', { key: 'cred', style: { animation: 'sbUp .3s ease' } }, [ inner = h('div', { key: 'cred', style: { animation: 'sbUp .3s ease' } }, [
backButton('cback'), backButton('cback'),
h('h2', { key: 'h', style: { margin: '0 0 6px', fontSize: '23px', fontWeight: 800, letterSpacing: '-0.5px', color: 'var(--sb-text-1)' } }, isCreate ? t('handshake.shareTitle') : t('handshake.sendAnswerTitle')), h('h2', { key: 'h', style: { margin: '0 0 8px', fontSize: '26px', fontWeight: 800, letterSpacing: '-0.7px', color: 'var(--sb-text-1)' } }, isCreate ? t('handshake.shareTitle') : t('handshake.sendAnswerTitle')),
h('p', { key: 'p', style: { margin: '0 0 18px', fontSize: '14px', lineHeight: 1.55, color: 'var(--sb-text-7)' } }, isCreate ? t('handshake.shareDesc') : t('handshake.sendAnswerDesc')), h('p', { key: 'p', style: { margin: '0 0 24px', fontSize: '14px', lineHeight: 1.55, color: 'var(--sb-text-7)' } }, isCreate ? t('handshake.shareDesc') : t('handshake.sendAnswerDesc')),
showQrButton, credCard,
credBlock, isOfferCred && h('div', { key: 'offerextra' }, [
isOfferCred && h('div', { key: 'offerextra', style: { marginTop: '4px' } }, [ h('div', { key: 'lbl', style: { fontSize: '13.5px', fontWeight: 600, color: 'var(--sb-text-3)', marginBottom: '10px' } }, t('handshake.thenReceive')),
h('div', { key: 'lbl', style: { fontSize: '12.5px', fontWeight: 600, color: 'var(--sb-text-6)', marginBottom: '8px' } }, t('handshake.thenReceive')), h('textarea', { key: 'ta', dir: 'ltr', value: answerInput, onChange: (e) => { setAnswerInput(e.target.value); if (e.target.value.trim().length > 0 && typeof markAnswerCreated === 'function') markAnswerCreated(); }, rows: 3, placeholder: t('handshake.pasteAnswerPlaceholder'), style: { display: 'block', width: '100%', resize: 'none', borderRadius: '14px', border: `1px solid ${hasAnswer ? 'rgba(var(--sb-orange-rgb), 0.45)' : 'rgba(var(--sb-ink), 0.08)'}`, background: 'var(--sb-surface)', outline: 'none', color: 'var(--sb-text-2)', fontFamily: MONO, fontSize: '12.5px', lineHeight: 1.5, padding: '13px 15px', marginBottom: '12px', transition: 'border-color .15s' } }),
h('div', { key: 'ta', style: { borderRadius: '12px', border: `1px solid ${hasAnswer ? 'rgba(var(--sb-ink), 0.18)' : 'rgba(var(--sb-ink), 0.07)'}`, background: 'var(--sb-surface)', padding: '11px 14px', marginBottom: '10px' } },
h('textarea', { dir: 'ltr', value: answerInput, onChange: (e) => { setAnswerInput(e.target.value); if (e.target.value.trim().length > 0 && typeof markAnswerCreated === 'function') markAnswerCreated(); }, rows: 2, placeholder: t('handshake.pasteAnswerPlaceholder'), style: { width: '100%', resize: 'none', border: 'none', outline: 'none', background: 'transparent', color: 'var(--sb-text-3)', fontFamily: MONO, fontSize: '12px', lineHeight: 1.55, minHeight: '44px' } })),
h('div', { key: 'btns', style: { display: 'flex', gap: '10px' } }, [ h('div', { key: 'btns', style: { display: 'flex', gap: '10px' } }, [
h('button', { key: 'scan', className: 'sb-scan-btn', onClick: () => setShowQRScannerModal(true), style: { flex: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '14px 16px', borderRadius: '13px', border: '1px solid rgba(var(--sb-ink), 0.1)', background: 'rgba(var(--sb-ink), 0.04)', color: 'var(--sb-text-4)', fontFamily: 'inherit', fontSize: '14px', fontWeight: 700, cursor: 'pointer' } }, [fa('fa-camera', { key: 'i' }), t('action.scan')]), h('button', { key: 'scan', className: 'sb-scan-btn', onClick: () => setShowQRScannerModal(true), style: { flex: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '0 18px', height: '50px', borderRadius: '13px', border: '1px solid rgba(var(--sb-ink), 0.1)', background: 'transparent', color: 'var(--sb-text-2)', fontFamily: 'inherit', fontSize: '14px', fontWeight: 700, cursor: 'pointer' } }, [
h('button', { key: 'est', onClick: onConnect, disabled: !hasAnswer, style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '9px', padding: '14px', borderRadius: '13px', border: 'none', background: hasAnswer ? C_ORANGE_SOLID : 'rgba(var(--sb-ink), 0.05)', color: hasAnswer ? 'var(--sb-on-accent)' : 'var(--sb-text-faint)', fontFamily: 'inherit', fontSize: '14.5px', fontWeight: 700, cursor: hasAnswer ? 'pointer' : 'not-allowed', boxShadow: hasAnswer ? '0 8px 24px rgba(var(--sb-orange-rgb), 0.28)' : 'none' } }, t('handshake.establish')) h('svg', { key: 'i', width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true },
h('path', { d: 'M4 8V5a1 1 0 0 1 1-1h3M16 4h3a1 1 0 0 1 1 1v3M20 16v3a1 1 0 0 1-1 1h-3M8 20H5a1 1 0 0 1-1-1v-3M7 12h10' })),
t('action.scan')
]),
h('button', { key: 'est', onClick: onConnect, disabled: !hasAnswer, style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '9px', height: '50px', borderRadius: '13px', border: 'none', background: hasAnswer ? C_ORANGE_SOLID : 'rgba(var(--sb-ink), 0.05)', color: hasAnswer ? 'var(--sb-on-accent)' : 'var(--sb-text-faint)', fontFamily: 'inherit', fontSize: '14.5px', fontWeight: 700, cursor: hasAnswer ? 'pointer' : 'not-allowed', transition: 'background .2s, color .2s' } }, t('handshake.establish'))
]) ])
]), ]),
isAnswerCred && h('div', { key: 'answerextra', style: { marginTop: '4px', display: 'flex', alignItems: 'center', gap: '10px', padding: '12px 14px', borderRadius: '12px', border: '1px solid rgba(var(--sb-green-rgb), 0.18)', background: 'rgba(var(--sb-green-rgb), 0.05)' } }, [ isAnswerCred && h('div', { key: 'answerextra', style: { display: 'flex', alignItems: 'center', gap: '12px', padding: '4px 2px' } }, [
fa('fa-circle-notch', { key: 'i', color: C_GREEN_SOLID, animation: 'sbSpin 1.4s linear infinite' }), h('span', { key: 'i', style: { flex: 'none', width: '16px', height: '16px', borderRadius: '50%', border: '2px solid rgba(var(--sb-ink), 0.1)', borderTopColor: C_ORANGE_SOLID, animation: 'sbSpin .9s linear infinite' } }),
h('span', { key: 't', style: { fontSize: '13px', color: 'var(--sb-text-4)', fontWeight: 500 } }, t('handshake.answerSentNote')) h('span', { key: 't', style: { fontSize: '13px', lineHeight: 1.5, color: 'var(--sb-text-7)' } }, t('handshake.answerSentNote'))
]) ])
]); ]);
} else if (isCreate) { } else if (isCreate) {
@@ -1669,26 +1764,30 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
h('button', { key: 'gen', className: 'sb-gen-btn', onClick: () => { requestNotificationPermissionOnInteraction(); if (webrtcManagerRef.current) handleCreateOffer(); }, style: { width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '9px', padding: '15px', borderRadius: '13px', border: 'none', background: C_ORANGE_SOLID, color: 'var(--sb-on-accent)', fontFamily: 'inherit', fontSize: '15px', fontWeight: 700, cursor: 'pointer', boxShadow: '0 8px 24px rgba(var(--sb-orange-rgb), 0.28)' } }, [fa('fa-bolt', { key: 'i' }), t('intro.createCta')]) h('button', { key: 'gen', className: 'sb-gen-btn', onClick: () => { requestNotificationPermissionOnInteraction(); if (webrtcManagerRef.current) handleCreateOffer(); }, style: { width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '9px', padding: '15px', borderRadius: '13px', border: 'none', background: C_ORANGE_SOLID, color: 'var(--sb-on-accent)', fontFamily: 'inherit', fontSize: '15px', fontWeight: 700, cursor: 'pointer', boxShadow: '0 8px 24px rgba(var(--sb-orange-rgb), 0.28)' } }, [fa('fa-bolt', { key: 'i' }), t('intro.createCta')])
]); ]);
} else { } else {
// JOIN intro // JOIN intro — same card language as the invitation/answer screens:
// scan row on top, the paste field below, one bordered surface.
const canJoin = hasInvite && connectionStatus !== 'connecting';
inner = h('div', { key: 'introJ', style: { animation: 'sbUp .28s ease' } }, [ inner = h('div', { key: 'introJ', style: { animation: 'sbUp .28s ease' } }, [
h('h2', { key: 'h', style: { margin: '0 0 6px', fontSize: '23px', fontWeight: 800, letterSpacing: '-0.5px', color: 'var(--sb-text-1)' } }, t('intro.joinTitle')), h('h2', { key: 'h', style: { margin: '0 0 8px', fontSize: '26px', fontWeight: 800, letterSpacing: '-0.7px', color: 'var(--sb-text-1)' } }, t('intro.joinTitle')),
h('p', { key: 'p', style: { margin: '0 0 16px', fontSize: '14px', lineHeight: 1.55, color: 'var(--sb-text-7)' } }, "Scan your peer's QR with your camera, or paste their invitation code."), h('p', { key: 'p', style: { margin: '0 0 24px', fontSize: '14px', lineHeight: 1.55, color: 'var(--sb-text-7)' } }, t('intro.joinDesc')),
h('button', { key: 'scan', className: 'sb-scan-btn', onClick: () => { requestNotificationPermissionOnInteraction(); setShowQRScannerModal(true); }, style: { width: '100%', display: 'flex', alignItems: 'center', gap: '13px', padding: '15px 16px', borderRadius: '14px', border: '1px solid rgba(var(--sb-green-rgb), 0.3)', background: 'rgba(var(--sb-green-rgb), 0.06)', color: 'inherit', fontFamily: 'inherit', cursor: 'pointer', textAlign: 'start', marginBottom: '14px' } }, [ h('div', { key: 'card', style: { borderRadius: '16px', border: '1px solid rgba(var(--sb-ink), 0.07)', background: 'var(--sb-surface)', overflow: 'hidden', marginBottom: '12px' } }, [
h('span', { key: 'ic', style: { flex: 'none', width: '42px', height: '42px', borderRadius: '12px', display: 'grid', placeItems: 'center', background: 'rgba(var(--sb-green-rgb), 0.12)', border: '1px solid rgba(var(--sb-green-rgb), 0.28)' } }, fa('fa-camera', { color: C_GREEN_SOLID, fontSize: '18px' })), h('button', { key: 'scan', className: 'sb-qr-row', onClick: () => { requestNotificationPermissionOnInteraction(); setShowQRScannerModal(true); }, style: { width: '100%', display: 'flex', alignItems: 'center', gap: '14px', padding: '14px 16px', border: 'none', background: 'transparent', color: 'inherit', fontFamily: 'inherit', cursor: 'pointer', textAlign: 'start', transition: 'background .15s' } }, [
h('span', { key: 'tx', style: { flex: 1 } }, [ h('span', { key: 'ic', style: { flex: 'none', width: '48px', height: '48px', borderRadius: '9px', display: 'grid', placeItems: 'center', background: 'rgba(var(--sb-ink), 0.05)', border: '1px solid rgba(var(--sb-ink), 0.08)', color: 'var(--sb-text-2)' } },
h('span', { key: 't', style: { display: 'block', fontSize: '14.5px', fontWeight: 700, color: 'var(--sb-text-1)' } }, t('intro.scanTitle')), h('svg', { width: 22, height: 22, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true },
h('span', { key: 's', style: { display: 'block', fontSize: '12.5px', color: 'var(--sb-text-7)', marginTop: '1px' } }, t('intro.scanSubtitle')) h('path', { d: 'M4 8V5a1 1 0 0 1 1-1h3M16 4h3a1 1 0 0 1 1 1v3M20 16v3a1 1 0 0 1-1 1h-3M8 20H5a1 1 0 0 1-1-1v-3M7 12h10' }))),
h('span', { key: 'tx', style: { flex: 1, minWidth: 0 } }, [
h('span', { key: 't', style: { display: 'block', fontSize: '15px', fontWeight: 700, color: 'var(--sb-text-1)' } }, t('intro.scanTitle')),
h('span', { key: 's', style: { display: 'block', fontSize: '12.5px', color: 'var(--sb-text-7)', marginTop: '2px' } }, t('intro.scanSubtitle'))
]),
fa('fa-chevron-right', { color: 'var(--sb-text-9)' })
]), ]),
fa('fa-chevron-right', { color: 'var(--sb-text-9)' }) h('div', { key: 'div', style: { height: '1px', background: 'rgba(var(--sb-ink), 0.06)' } }),
h('div', { key: 'paste', style: { padding: '12px 16px 16px' } }, [
h('div', { key: 'l', style: { fontSize: '12.5px', fontWeight: 600, color: 'var(--sb-text-6)', marginBottom: '10px' } }, t('intro.orPasteCode')),
h('textarea', { key: 'ta', dir: 'ltr', value: offerInput, onChange: (e) => { setOfferInput(e.target.value); if (e.target.value.trim().length > 0 && typeof markAnswerCreated === 'function') markAnswerCreated(); }, rows: 3, placeholder: t('intro.pastePlaceholder'), style: { display: 'block', width: '100%', resize: 'none', borderRadius: '11px', border: `1px solid ${hasInvite ? 'rgba(var(--sb-orange-rgb), 0.45)' : 'rgba(var(--sb-ink), 0.05)'}`, background: 'var(--sb-bg)', outline: 'none', color: 'var(--sb-text-2)', fontFamily: MONO, fontSize: '12.5px', lineHeight: 1.5, padding: '11px 13px', transition: 'border-color .15s' } })
])
]), ]),
h('div', { key: 'or', style: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '14px' } }, [ h('button', { key: 'connect', onClick: () => { requestNotificationPermissionOnInteraction(); onCreateAnswer(); }, disabled: !canJoin, style: { width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '9px', height: '50px', borderRadius: '13px', border: 'none', background: canJoin ? C_ORANGE_SOLID : 'rgba(var(--sb-ink), 0.05)', color: canJoin ? 'var(--sb-on-accent)' : 'var(--sb-text-faint)', fontFamily: 'inherit', fontSize: '14.5px', fontWeight: 700, cursor: canJoin ? 'pointer' : 'not-allowed', transition: 'background .2s, color .2s' } }, connectionStatus === 'connecting' ? t('intro.connecting') : t('intro.connect'))
h('span', { key: 'a', style: { flex: 1, height: '1px', background: 'rgba(var(--sb-ink), 0.07)' } }),
h('span', { key: 'm', style: { fontSize: '11px', fontWeight: 600, color: 'var(--sb-text-faint)', textTransform: 'uppercase', letterSpacing: '0.7px' } }, t('intro.orPasteCode')),
h('span', { key: 'b', style: { flex: 1, height: '1px', background: 'rgba(var(--sb-ink), 0.07)' } })
]),
h('div', { key: 'ta', style: { borderRadius: '13px', border: `1px solid ${hasInvite ? 'rgba(var(--sb-ink), 0.18)' : 'rgba(var(--sb-ink), 0.07)'}`, background: 'var(--sb-surface)', padding: '13px 15px', marginBottom: '12px' } },
h('textarea', { dir: 'ltr', value: offerInput, onChange: (e) => { setOfferInput(e.target.value); if (e.target.value.trim().length > 0 && typeof markAnswerCreated === 'function') markAnswerCreated(); }, rows: 3, placeholder: t('intro.pastePlaceholder'), style: { width: '100%', resize: 'none', border: 'none', outline: 'none', background: 'transparent', color: 'var(--sb-text-3)', fontFamily: MONO, fontSize: '12.5px', lineHeight: 1.6, minHeight: '66px' } })),
h('button', { key: 'connect', onClick: () => { requestNotificationPermissionOnInteraction(); onCreateAnswer(); }, disabled: !hasInvite || connectionStatus === 'connecting', style: { width: '100%', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '9px', padding: '14px', borderRadius: '13px', border: 'none', background: (hasInvite && connectionStatus !== 'connecting') ? C_ORANGE_SOLID : 'rgba(var(--sb-ink), 0.05)', color: (hasInvite && connectionStatus !== 'connecting') ? 'var(--sb-on-accent)' : 'var(--sb-text-faint)', fontFamily: 'inherit', fontSize: '15px', fontWeight: 700, cursor: (hasInvite && connectionStatus !== 'connecting') ? 'pointer' : 'not-allowed', boxShadow: (hasInvite && connectionStatus !== 'connecting') ? '0 8px 24px rgba(var(--sb-orange-rgb), 0.28)' : 'none' } }, connectionStatus === 'connecting' ? t('intro.connecting') : t('intro.connect'))
]); ]);
} }
@@ -1704,7 +1803,7 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
// /download/v0.3.0/SecureBit.Chat_0.1.0_x64-setup.exe, a file that never // /download/v0.3.0/SecureBit.Chat_0.1.0_x64-setup.exe, a file that never
// existed, and the download 404s. A pinned tag keeps serving a real // existed, and the download 404s. A pinned tag keeps serving a real
// installer instead. // installer instead.
const SB_DESKTOP_VERSION = '1.0.4'; const SB_DESKTOP_VERSION = '1.0.5';
const SB_DESKTOP_RELEASE = `https://github.com/SecureBitChat/securebit-desktop/releases/download/v${SB_DESKTOP_VERSION}`; const SB_DESKTOP_RELEASE = `https://github.com/SecureBitChat/securebit-desktop/releases/download/v${SB_DESKTOP_VERSION}`;
const DOWNLOADS = { const DOWNLOADS = {
mac: { name: 'macOS', format: '.dmg · Apple Silicon & Intel', icon: 'fab fa-apple', url: `${SB_DESKTOP_RELEASE}/SecureBit.Chat_${SB_DESKTOP_VERSION}_x64.dmg` }, mac: { name: 'macOS', format: '.dmg · Apple Silicon & Intel', icon: 'fab fa-apple', url: `${SB_DESKTOP_RELEASE}/SecureBit.Chat_${SB_DESKTOP_VERSION}_x64.dmg` },
@@ -1847,6 +1946,15 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
'@keyframes sbNode{0%,100%{box-shadow:0 0 0 0 rgba(var(--sb-green-rgb), 0)}50%{box-shadow:0 0 0 6px rgba(var(--sb-green-rgb), .06)}}' + '@keyframes sbNode{0%,100%{box-shadow:0 0 0 0 rgba(var(--sb-green-rgb), 0)}50%{box-shadow:0 0 0 6px rgba(var(--sb-green-rgb), .06)}}' +
'@keyframes sbScan{0%{top:8%}100%{top:88%}}' + '@keyframes sbScan{0%{top:8%}100%{top:88%}}' +
'@keyframes sbBlink{0%,100%{opacity:1}50%{opacity:.35}}' + '@keyframes sbBlink{0%,100%{opacity:1}50%{opacity:.35}}' +
// SAS verification step (SAS Verification.dc.html).
'@keyframes otpPop{0%{transform:translateY(6px) scale(.9);opacity:0}60%{transform:translateY(-1px) scale(1.04);opacity:1}100%{transform:none;opacity:1}}' +
'@keyframes otpOk{0%{transform:none}45%{transform:translateY(-4px)}100%{transform:none;background:rgba(var(--sb-green-rgb),.07);border-color:rgba(var(--sb-green-rgb),.55);color:var(--sb-green)}}' +
'@keyframes otpShake{0%,100%{transform:translateX(0)}20%{transform:translateX(-7px)}40%{transform:translateX(6px)}60%{transform:translateX(-4px)}80%{transform:translateX(2px)}}' +
'@keyframes otpCaret{0%,100%{opacity:1}50%{opacity:0}}' +
'@keyframes svDot{0%,80%,100%{opacity:.25;transform:translateY(0)}40%{opacity:1;transform:translateY(-3px)}}' +
'@keyframes svDraw{from{stroke-dashoffset:24}to{stroke-dashoffset:0}}' +
'@keyframes svScan{from{left:-30%}to{left:100%}}' +
'@keyframes svFade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}' +
// P2P / mesh animation (left panel). One 14s loop: the direct line is // P2P / mesh animation (left panel). One 14s loop: the direct line is
// already up, then two peers join it. `sbTrav` moves a dot along the // already up, then two peers join it. `sbTrav` moves a dot along the
// same geometry the line is drawn from, via offset-path, so the packet // same geometry the line is drawn from, via offset-path, so the packet
+1 -1
View File
@@ -5,7 +5,7 @@
// The tag is pinned rather than using /releases/latest/download/: the filenames // The tag is pinned rather than using /releases/latest/download/: the filenames
// carry the version, so a `latest` link 404s the moment a newer release exists, // carry the version, so a `latest` link 404s the moment a newer release exists,
// whereas a pinned tag keeps serving a working (if older) installer. // whereas a pinned tag keeps serving a working (if older) installer.
const DESKTOP_VERSION = '1.0.4'; const DESKTOP_VERSION = '1.0.5';
const DESKTOP_RELEASE = `https://github.com/SecureBitChat/securebit-desktop/releases/download/v${DESKTOP_VERSION}`; const DESKTOP_RELEASE = `https://github.com/SecureBitChat/securebit-desktop/releases/download/v${DESKTOP_VERSION}`;
const DownloadApps = () => { const DownloadApps = () => {
+16 -12
View File
@@ -93,9 +93,10 @@ export const DICTIONARY = {
"intro.createDesc": "يولّد جهازك المفاتيح ودعوة تُستعمل مرّة واحدة. لا شيء يمرّ عبر خادم.", "intro.createDesc": "يولّد جهازك المفاتيح ودعوة تُستعمل مرّة واحدة. لا شيء يمرّ عبر خادم.",
"intro.createCta": "توليد المفاتيح والدعوة", "intro.createCta": "توليد المفاتيح والدعوة",
"intro.joinTitle": "انضمّ إلى قناة", "intro.joinTitle": "انضمّ إلى قناة",
"intro.joinDesc": "امسح رمز QR الخاص بالطرف الآخر بالكاميرا، أو الصق رمز الدعوة.",
"intro.scanTitle": "امسح رمز QR بالكاميرا", "intro.scanTitle": "امسح رمز QR بالكاميرا",
"intro.scanSubtitle": "الأسرع - وجّهها إلى شاشة الطرف الآخر", "intro.scanSubtitle": "الأسرع - وجّهها إلى شاشة الطرف الآخر",
"intro.orPasteCode": "أو ألصق الرمز", "intro.orPasteCode": "أو الصق الرمز",
"intro.pastePlaceholder": "ألصق رمز الدعوة هنا…", "intro.pastePlaceholder": "ألصق رمز الدعوة هنا…",
"intro.connect": "اتّصل", "intro.connect": "اتّصل",
"intro.connecting": "جارٍ المعالجة…", "intro.connecting": "جارٍ المعالجة…",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "إنشاء الاتصال", "handshake.establish": "إنشاء الاتصال",
"handshake.thenReceive": "ثمّ استقبل الردّ الذي يرسله الطرف الآخر", "handshake.thenReceive": "ثمّ استقبل الردّ الذي يرسله الطرف الآخر",
"handshake.pasteAnswerPlaceholder": "ألصق رمز ردّ الطرف الآخر…", "handshake.pasteAnswerPlaceholder": "ألصق رمز ردّ الطرف الآخر…",
"handshake.answerSentNote": "أرسل هذا الردّ إلى المُنشئ ثمّ انتظر - تُفتح الدردشة حالما يتّصل.", "handshake.answerSentNote": "بانتظار اتصال المُنشئ. ستُفتح المحادثة تلقائياً.",
"handshake.qrHint": "أبقِ هذا مفتوحاً حتى يلتقط الطرف الآخر الرمز.", "handshake.qrHint": "أبقِ هذا مفتوحاً حتى يلتقط الطرف الآخر الرمز.",
"handshake.qrHintFrames": "المصافحة موزّعة على {frames} إطارات - أبقِ هذا مفتوحاً حتى يلتقطها الطرف الآخر كلّها.", "handshake.qrHintFrames": "المصافحة موزّعة على {frames} إطارات - أبقِ هذا مفتوحاً حتى يلتقطها الطرف الآخر كلّها.",
"verify.title": "التحقّق الأمني",
"verify.desc": "قارِن رمز الأمان هذا مع الطرف الآخر عبر قناة منفصلة (مكالمة أو لقاء مباشر)، ثمّ اكتبه لفتح الدردشة.",
"verify.enterLabel": "أدخل الرمز المُتحقَّق منه",
"verify.placeholder": "اكتب الرمز هنا", "verify.placeholder": "اكتب الرمز هنا",
"verify.confirm": "تأكيد الرمز", "verify.confirm": "تأكيد الرمز",
"verify.confirmed": "مؤكَّد", "verify.confirmed": "مؤكَّد",
"verify.mismatch": "غير متطابقين",
"verify.yours": "تأكيدك",
"verify.peer": "تأكيد الطرف الآخر",
"verify.pending": "قيد الانتظار", "verify.pending": "قيد الانتظار",
"verify.waiting": "بانتظار الرمز…", "verify.waiting": "بانتظار الرمز…",
"verify.verified": "تمّ التحقّق من القناة", "sasv.title": "تحقّق من الطرف الآخر",
"verify.bothConfirmed": "أكّد الطرفان. جارٍ فتح الدردشة الآمنة…", "sasv.titleDone": "تم التحقّق من الطرف الآخر",
"sasv.desc": "اقرأ الرمز بصوت عالٍ عبر مكالمة أو وجهًا لوجه. لا تتابع إلا إذا رأى الطرف الآخر الأرقام نفسها.",
"sasv.descDone": "أكّدتما كلاكما الرمز نفسه. الاتصال موثَّق.",
"sasv.enter": "أدخل الرمز",
"sasv.match": "متطابق",
"sasv.noMatch": "غير متطابق",
"sasv.you": "أنت",
"sasv.peer": "الطرف الآخر",
"sasv.waitingPeer": "بانتظار الطرف الآخر…",
"sasv.codesDiffer": "الرموز غير متطابقة",
"sasv.opening": "جارٍ فتح المحادثة الآمنة",
"pwa.bannerTitle": "ثبّت SecureBit.chat", "pwa.bannerTitle": "ثبّت SecureBit.chat",
"pwa.bannerDesc": "احصل على تجربة تطبيق أصلي بأمان أعلى", "pwa.bannerDesc": "احصل على تجربة تطبيق أصلي بأمان أعلى",
"pwa.install": "تثبيت", "pwa.install": "تثبيت",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "مسح", "action.scan": "مسح",
"action.downloadDesktop": "نزّل تطبيق سطح المكتب", "action.downloadDesktop": "نزّل تطبيق سطح المكتب",
"action.advancedSettings": "إعدادات متقدّمة", "action.advancedSettings": "إعدادات متقدّمة",
"cred.offerTag": "عرض · أو انسخ النصّ",
"cred.answerTag": "ردّ · أو انسخ النصّ",
"cred.reveal": "اضغط للكشف - أبقِ هذا الرمز سرّياً", "cred.reveal": "اضغط للكشف - أبقِ هذا الرمز سرّياً",
"cred.orCopy": "أو انسخ الرمز",
"qr.title": "امسح رمز QR", "qr.title": "امسح رمز QR",
"qr.subtitle": "وجّه كاميرتك إلى رمزهم", "qr.subtitle": "وجّه كاميرتك إلى رمزهم",
"qr.scanning": "جارٍ المسح…", "qr.scanning": "جارٍ المسح…",
+16 -12
View File
@@ -93,9 +93,10 @@ export const DICTIONARY = {
"intro.createDesc": "Ihr Gerät erzeugt die Schlüssel und eine einmalige Einladung. Nichts davon berührt einen Server.", "intro.createDesc": "Ihr Gerät erzeugt die Schlüssel und eine einmalige Einladung. Nichts davon berührt einen Server.",
"intro.createCta": "Schlüssel & Einladung erzeugen", "intro.createCta": "Schlüssel & Einladung erzeugen",
"intro.joinTitle": "Kanal beitreten", "intro.joinTitle": "Kanal beitreten",
"intro.joinDesc": "Scannen Sie den QR-Code Ihres Gegenübers mit der Kamera oder fügen Sie seinen Einladungscode ein.",
"intro.scanTitle": "QR-Code mit der Kamera scannen", "intro.scanTitle": "QR-Code mit der Kamera scannen",
"intro.scanSubtitle": "Am schnellsten - auf den Bildschirm Ihres Gegenübers halten", "intro.scanSubtitle": "Am schnellsten - auf den Bildschirm Ihres Gegenübers halten",
"intro.orPasteCode": "oder Code einfügen", "intro.orPasteCode": "Oder den Code einfügen",
"intro.pastePlaceholder": "Einladungscode hier einfügen …", "intro.pastePlaceholder": "Einladungscode hier einfügen …",
"intro.connect": "Verbinden", "intro.connect": "Verbinden",
"intro.connecting": "Wird verarbeitet …", "intro.connecting": "Wird verarbeitet …",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "Verbindung herstellen", "handshake.establish": "Verbindung herstellen",
"handshake.thenReceive": "Nehmen Sie danach die Antwort Ihres Gegenübers entgegen", "handshake.thenReceive": "Nehmen Sie danach die Antwort Ihres Gegenübers entgegen",
"handshake.pasteAnswerPlaceholder": "Antwortcode des Gegenübers einfügen …", "handshake.pasteAnswerPlaceholder": "Antwortcode des Gegenübers einfügen …",
"handshake.answerSentNote": "Senden Sie diese Antwort an den Ersteller und warten Sie - der Chat öffnet sich, sobald er sich verbindet.", "handshake.answerSentNote": "Warten, bis sich der Ersteller verbindet. Der Chat öffnet sich automatisch.",
"handshake.qrHint": "Lassen Sie dies geöffnet, bis Ihr Gegenüber den Code erfasst hat.", "handshake.qrHint": "Lassen Sie dies geöffnet, bis Ihr Gegenüber den Code erfasst hat.",
"handshake.qrHintFrames": "Der Handschlag ist auf {frames} Einzelbilder verteilt - lassen Sie dies geöffnet, bis Ihr Gegenüber alle erfasst hat.", "handshake.qrHintFrames": "Der Handschlag ist auf {frames} Einzelbilder verteilt - lassen Sie dies geöffnet, bis Ihr Gegenüber alle erfasst hat.",
"verify.title": "Sicherheitsprüfung",
"verify.desc": "Vergleichen Sie diesen Sicherheitscode über einen anderen Weg mit Ihrem Gegenüber (Anruf oder persönlich) und geben Sie ihn dann ein, um den Chat freizuschalten.",
"verify.enterLabel": "Geprüften Code eingeben",
"verify.placeholder": "Code hier eingeben", "verify.placeholder": "Code hier eingeben",
"verify.confirm": "Code bestätigen", "verify.confirm": "Code bestätigen",
"verify.confirmed": "Bestätigt", "verify.confirmed": "Bestätigt",
"verify.mismatch": "Stimmt nicht überein",
"verify.yours": "Ihre Bestätigung",
"verify.peer": "Bestätigung des Gegenübers",
"verify.pending": "Ausstehend", "verify.pending": "Ausstehend",
"verify.waiting": "Warten auf den Code …", "verify.waiting": "Warten auf den Code …",
"verify.verified": "Kanal geprüft", "sasv.title": "Gegenüber überprüfen",
"verify.bothConfirmed": "Beide Seiten haben bestätigt. Der sichere Chat wird geöffnet …", "sasv.titleDone": "Gegenüber verifiziert",
"sasv.desc": "Lies den Code bei einem Anruf oder persönlich laut vor. Fahre nur fort, wenn dein Gegenüber dieselben Ziffern sieht.",
"sasv.descDone": "Ihr habt beide denselben Code bestätigt. Die Verbindung ist authentifiziert.",
"sasv.enter": "Code eingeben",
"sasv.match": "Stimmt überein",
"sasv.noMatch": "Stimmt nicht überein",
"sasv.you": "Du",
"sasv.peer": "Gegenüber",
"sasv.waitingPeer": "Warte auf Gegenüber…",
"sasv.codesDiffer": "Codes stimmen nicht überein",
"sasv.opening": "Sicherer Chat wird geöffnet",
"pwa.bannerTitle": "SecureBit.chat installieren", "pwa.bannerTitle": "SecureBit.chat installieren",
"pwa.bannerDesc": "Die App-Erfahrung mit zusätzlicher Sicherheit", "pwa.bannerDesc": "Die App-Erfahrung mit zusätzlicher Sicherheit",
"pwa.install": "Installieren", "pwa.install": "Installieren",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "Scannen", "action.scan": "Scannen",
"action.downloadDesktop": "Desktop-App laden", "action.downloadDesktop": "Desktop-App laden",
"action.advancedSettings": "Erweiterte Einstellungen", "action.advancedSettings": "Erweiterte Einstellungen",
"cred.offerTag": "Angebot · oder Text kopieren",
"cred.answerTag": "Antwort · oder Text kopieren",
"cred.reveal": "Zum Anzeigen klicken - halten Sie diesen Code geheim", "cred.reveal": "Zum Anzeigen klicken - halten Sie diesen Code geheim",
"cred.orCopy": "Oder den Code kopieren",
"qr.title": "QR-Code scannen", "qr.title": "QR-Code scannen",
"qr.subtitle": "Richten Sie die Kamera auf den QR-Code", "qr.subtitle": "Richten Sie die Kamera auf den QR-Code",
"qr.scanning": "Wird gescannt …", "qr.scanning": "Wird gescannt …",
+16 -12
View File
@@ -93,9 +93,10 @@ export const DICTIONARY = {
"intro.createDesc": "Your device generates the keys and a one-time invitation. Nothing touches a server.", "intro.createDesc": "Your device generates the keys and a one-time invitation. Nothing touches a server.",
"intro.createCta": "Generate keys & invitation", "intro.createCta": "Generate keys & invitation",
"intro.joinTitle": "Join a channel", "intro.joinTitle": "Join a channel",
"intro.joinDesc": "Scan your peer's QR with your camera, or paste their invitation code.",
"intro.scanTitle": "Scan QR with camera", "intro.scanTitle": "Scan QR with camera",
"intro.scanSubtitle": "Fastest - point at your peer's screen", "intro.scanSubtitle": "Fastest - point at your peer's screen",
"intro.orPasteCode": "or paste code", "intro.orPasteCode": "Or paste the code",
"intro.pastePlaceholder": "Paste invitation code here…", "intro.pastePlaceholder": "Paste invitation code here…",
"intro.connect": "Connect", "intro.connect": "Connect",
"intro.connecting": "Processing…", "intro.connecting": "Processing…",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "Establish connection", "handshake.establish": "Establish connection",
"handshake.thenReceive": "Then receive the answer your peer sends back", "handshake.thenReceive": "Then receive the answer your peer sends back",
"handshake.pasteAnswerPlaceholder": "Paste peer's answer code…", "handshake.pasteAnswerPlaceholder": "Paste peer's answer code…",
"handshake.answerSentNote": "Send this answer to the creator, then wait - the chat opens once they connect.", "handshake.answerSentNote": "Waiting for the creator to connect. The chat opens automatically.",
"handshake.qrHint": "Keep this open until your peer captures the code.", "handshake.qrHint": "Keep this open until your peer captures the code.",
"handshake.qrHintFrames": "The handshake is split across {frames} frames - keep this open until your peer captures all of them.", "handshake.qrHintFrames": "The handshake is split across {frames} frames - keep this open until your peer captures all of them.",
"verify.title": "Security verification",
"verify.desc": "Compare this safety code with your peer over a separate channel (voice / in person), then type it to unlock the chat.",
"verify.enterLabel": "Enter the verified code",
"verify.placeholder": "Type code here", "verify.placeholder": "Type code here",
"verify.confirm": "Confirm code", "verify.confirm": "Confirm code",
"verify.confirmed": "Confirmed", "verify.confirmed": "Confirmed",
"verify.mismatch": "Don't match",
"verify.yours": "Your confirmation",
"verify.peer": "Peer confirmation",
"verify.pending": "Pending", "verify.pending": "Pending",
"verify.waiting": "Waiting for code…", "verify.waiting": "Waiting for code…",
"verify.verified": "Channel verified", "sasv.title": "Verify your peer",
"verify.bothConfirmed": "Both parties confirmed. Opening the secure chat…", "sasv.titleDone": "Peer verified",
"sasv.desc": "Read the code aloud over a call or in person. Continue only if your peer sees the same digits.",
"sasv.descDone": "You both confirmed the same code. The connection is authenticated.",
"sasv.enter": "Enter the code",
"sasv.match": "Match",
"sasv.noMatch": "Doesn't match",
"sasv.you": "You",
"sasv.peer": "Peer",
"sasv.waitingPeer": "Waiting for peer…",
"sasv.codesDiffer": "Codes don't match",
"sasv.opening": "Opening secure chat",
"pwa.bannerTitle": "Install SecureBit.chat", "pwa.bannerTitle": "Install SecureBit.chat",
"pwa.bannerDesc": "Get the native app experience with enhanced security", "pwa.bannerDesc": "Get the native app experience with enhanced security",
"pwa.install": "Install", "pwa.install": "Install",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "Scan", "action.scan": "Scan",
"action.downloadDesktop": "Download desktop app", "action.downloadDesktop": "Download desktop app",
"action.advancedSettings": "Advanced settings", "action.advancedSettings": "Advanced settings",
"cred.offerTag": "offer · or copy text",
"cred.answerTag": "answer · or copy text",
"cred.reveal": "Click to reveal - keep this code private", "cred.reveal": "Click to reveal - keep this code private",
"cred.orCopy": "Or copy the code",
"qr.title": "Scan QR code", "qr.title": "Scan QR code",
"qr.subtitle": "Point your camera at their QR", "qr.subtitle": "Point your camera at their QR",
"qr.scanning": "Scanning…", "qr.scanning": "Scanning…",
+16 -12
View File
@@ -93,9 +93,10 @@ export const DICTIONARY = {
"intro.createDesc": "Tu dispositivo genera las claves y una invitación de un solo uso. Nada de esto pasa por un servidor.", "intro.createDesc": "Tu dispositivo genera las claves y una invitación de un solo uso. Nada de esto pasa por un servidor.",
"intro.createCta": "Generar claves e invitación", "intro.createCta": "Generar claves e invitación",
"intro.joinTitle": "Unirse a un canal", "intro.joinTitle": "Unirse a un canal",
"intro.joinDesc": "Escanea el QR de tu contacto con la cámara o pega su código de invitación.",
"intro.scanTitle": "Escanear el QR con la cámara", "intro.scanTitle": "Escanear el QR con la cámara",
"intro.scanSubtitle": "Lo más rápido: apunta a la pantalla de la otra persona", "intro.scanSubtitle": "Lo más rápido: apunta a la pantalla de la otra persona",
"intro.orPasteCode": "o pega el código", "intro.orPasteCode": "O pega el código",
"intro.pastePlaceholder": "Pega aquí el código de invitación…", "intro.pastePlaceholder": "Pega aquí el código de invitación…",
"intro.connect": "Conectar", "intro.connect": "Conectar",
"intro.connecting": "Procesando…", "intro.connecting": "Procesando…",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "Establecer la conexión", "handshake.establish": "Establecer la conexión",
"handshake.thenReceive": "Después recibe la respuesta que te devuelva la otra persona", "handshake.thenReceive": "Después recibe la respuesta que te devuelva la otra persona",
"handshake.pasteAnswerPlaceholder": "Pega aquí el código de respuesta…", "handshake.pasteAnswerPlaceholder": "Pega aquí el código de respuesta…",
"handshake.answerSentNote": "Envía esta respuesta a quien creó el canal y espera: el chat se abrirá cuando conecte.", "handshake.answerSentNote": "Esperando a que el creador se conecte. El chat se abrirá automáticamente.",
"handshake.qrHint": "Mantén esto abierto hasta que la otra persona capture el código.", "handshake.qrHint": "Mantén esto abierto hasta que la otra persona capture el código.",
"handshake.qrHintFrames": "El saludo se reparte en {frames} imágenes: mantén esto abierto hasta que las capture todas.", "handshake.qrHintFrames": "El saludo se reparte en {frames} imágenes: mantén esto abierto hasta que las capture todas.",
"verify.title": "Verificación de seguridad",
"verify.desc": "Compara este código de seguridad con la otra persona por otra vía (voz o en persona) y escríbelo para desbloquear el chat.",
"verify.enterLabel": "Introduce el código verificado",
"verify.placeholder": "Escribe el código aquí", "verify.placeholder": "Escribe el código aquí",
"verify.confirm": "Confirmar el código", "verify.confirm": "Confirmar el código",
"verify.confirmed": "Confirmado", "verify.confirmed": "Confirmado",
"verify.mismatch": "No coinciden",
"verify.yours": "Tu confirmación",
"verify.peer": "Confirmación de la otra persona",
"verify.pending": "Pendiente", "verify.pending": "Pendiente",
"verify.waiting": "Esperando el código…", "verify.waiting": "Esperando el código…",
"verify.verified": "Canal verificado", "sasv.title": "Verifica a tu contacto",
"verify.bothConfirmed": "Ambas partes han confirmado. Abriendo el chat seguro…", "sasv.titleDone": "Contacto verificado",
"sasv.desc": "Lee el código en voz alta por llamada o en persona. Continúa solo si tu contacto ve los mismos dígitos.",
"sasv.descDone": "Ambos confirmasteis el mismo código. La conexión está autenticada.",
"sasv.enter": "Introduce el código",
"sasv.match": "Coincide",
"sasv.noMatch": "No coincide",
"sasv.you": "Tú",
"sasv.peer": "Contacto",
"sasv.waitingPeer": "Esperando a tu contacto…",
"sasv.codesDiffer": "Los códigos no coinciden",
"sasv.opening": "Abriendo el chat seguro",
"pwa.bannerTitle": "Instala SecureBit.chat", "pwa.bannerTitle": "Instala SecureBit.chat",
"pwa.bannerDesc": "Disfruta de la experiencia de app nativa con más seguridad", "pwa.bannerDesc": "Disfruta de la experiencia de app nativa con más seguridad",
"pwa.install": "Instalar", "pwa.install": "Instalar",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "Escanear", "action.scan": "Escanear",
"action.downloadDesktop": "Descargar la app de escritorio", "action.downloadDesktop": "Descargar la app de escritorio",
"action.advancedSettings": "Ajustes avanzados", "action.advancedSettings": "Ajustes avanzados",
"cred.offerTag": "oferta · o copia el texto",
"cred.answerTag": "respuesta · o copia el texto",
"cred.reveal": "Haz clic para mostrarlo: mantén este código en privado", "cred.reveal": "Haz clic para mostrarlo: mantén este código en privado",
"cred.orCopy": "O copia el código",
"qr.title": "Escanear código QR", "qr.title": "Escanear código QR",
"qr.subtitle": "Apunta la cámara a su código QR", "qr.subtitle": "Apunta la cámara a su código QR",
"qr.scanning": "Escaneando…", "qr.scanning": "Escaneando…",
+16 -12
View File
@@ -93,9 +93,10 @@ export const DICTIONARY = {
"intro.createDesc": "دستگاه شما کلیدها و یک دعوت‌نامهٔ یک‌بارمصرف می‌سازد. هیچ‌چیز به سرور نمی‌رسد.", "intro.createDesc": "دستگاه شما کلیدها و یک دعوت‌نامهٔ یک‌بارمصرف می‌سازد. هیچ‌چیز به سرور نمی‌رسد.",
"intro.createCta": "ساخت کلید و دعوت‌نامه", "intro.createCta": "ساخت کلید و دعوت‌نامه",
"intro.joinTitle": "به کانالی بپیوندید", "intro.joinTitle": "به کانالی بپیوندید",
"intro.joinDesc": "QR طرف مقابل را با دوربین اسکن کنید یا کد دعوت او را جای‌گذاری کنید.",
"intro.scanTitle": "‏QR را با دوربین بخوانید", "intro.scanTitle": "‏QR را با دوربین بخوانید",
"intro.scanSubtitle": "سریع‌ترین راه - به صفحهٔ همتایتان بگیرید", "intro.scanSubtitle": "سریع‌ترین راه - به صفحهٔ همتایتان بگیرید",
"intro.orPasteCode": "یا کد را بچسبانید", "intro.orPasteCode": "یا کد را جای‌گذاری کنید",
"intro.pastePlaceholder": "کد دعوت را اینجا بچسبانید…", "intro.pastePlaceholder": "کد دعوت را اینجا بچسبانید…",
"intro.connect": "اتصال", "intro.connect": "اتصال",
"intro.connecting": "در حال پردازش…", "intro.connecting": "در حال پردازش…",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "برقراری اتصال", "handshake.establish": "برقراری اتصال",
"handshake.thenReceive": "سپس پاسخی را که همتایتان برمی‌گرداند دریافت کنید", "handshake.thenReceive": "سپس پاسخی را که همتایتان برمی‌گرداند دریافت کنید",
"handshake.pasteAnswerPlaceholder": "کد پاسخ همتا را بچسبانید…", "handshake.pasteAnswerPlaceholder": "کد پاسخ همتا را بچسبانید…",
"handshake.answerSentNote": "این پاسخ را برای سازنده بفرستید و منتظر بمانید - گفتگو به‌محض اتصال او باز می‌شود.", "handshake.answerSentNote": "در انتظار اتصال سازنده. گفتگو به‌طور خودکار باز می‌شود.",
"handshake.qrHint": "این را باز نگه دارید تا همتایتان کد را بگیرد.", "handshake.qrHint": "این را باز نگه دارید تا همتایتان کد را بگیرد.",
"handshake.qrHintFrames": "دست‌دادن میان {frames} فریم پخش شده است - این را باز نگه دارید تا همتایتان همهٔ آن‌ها را بگیرد.", "handshake.qrHintFrames": "دست‌دادن میان {frames} فریم پخش شده است - این را باز نگه دارید تا همتایتان همهٔ آن‌ها را بگیرد.",
"verify.title": "تأیید امنیتی",
"verify.desc": "این کد ایمنی را از راه کانالی جداگانه (تماس صوتی / حضوری) با همتایتان مقایسه کنید، سپس آن را بنویسید تا گفتگو باز شود.",
"verify.enterLabel": "کد تأییدشده را وارد کنید",
"verify.placeholder": "کد را اینجا بنویسید", "verify.placeholder": "کد را اینجا بنویسید",
"verify.confirm": "تأیید کد", "verify.confirm": "تأیید کد",
"verify.confirmed": "تأیید شد", "verify.confirmed": "تأیید شد",
"verify.mismatch": "هم‌خوان نیستند",
"verify.yours": "تأیید شما",
"verify.peer": "تأیید همتا",
"verify.pending": "در انتظار", "verify.pending": "در انتظار",
"verify.waiting": "در انتظار کد…", "verify.waiting": "در انتظار کد…",
"verify.verified": "کانال تأیید شد", "sasv.title": "طرف مقابل را تأیید کنید",
"verify.bothConfirmed": "هر دو طرف تأیید کردند. گفتگوی امن در حال باز شدن…", "sasv.titleDone": "طرف مقابل تأیید شد",
"sasv.desc": "کد را در تماس یا حضوری با صدای بلند بخوانید. فقط وقتی ادامه دهید که طرف مقابل همان ارقام را ببیند.",
"sasv.descDone": "هر دو یک کد را تأیید کردید. اتصال احراز هویت شد.",
"sasv.enter": "کد را وارد کنید",
"sasv.match": "مطابق است",
"sasv.noMatch": "مطابق نیست",
"sasv.you": "شما",
"sasv.peer": "طرف مقابل",
"sasv.waitingPeer": "در انتظار طرف مقابل…",
"sasv.codesDiffer": "کدها مطابق نیستند",
"sasv.opening": "در حال باز کردن گفتگوی امن",
"pwa.bannerTitle": "‏SecureBit.chat را نصب کنید", "pwa.bannerTitle": "‏SecureBit.chat را نصب کنید",
"pwa.bannerDesc": "تجربهٔ برنامهٔ بومی با امنیت بیشتر", "pwa.bannerDesc": "تجربهٔ برنامهٔ بومی با امنیت بیشتر",
"pwa.install": "نصب", "pwa.install": "نصب",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "خواندن", "action.scan": "خواندن",
"action.downloadDesktop": "دریافت برنامهٔ رومیزی", "action.downloadDesktop": "دریافت برنامهٔ رومیزی",
"action.advancedSettings": "تنظیمات پیشرفته", "action.advancedSettings": "تنظیمات پیشرفته",
"cred.offerTag": "پیشنهاد · یا متن را رونوشت کنید",
"cred.answerTag": "پاسخ · یا متن را رونوشت کنید",
"cred.reveal": "برای نمایش کلیک کنید - این کد را خصوصی نگه دارید", "cred.reveal": "برای نمایش کلیک کنید - این کد را خصوصی نگه دارید",
"cred.orCopy": "یا کد را رونوشت کنید",
"qr.title": "خواندن کد QR", "qr.title": "خواندن کد QR",
"qr.subtitle": "دوربین را به QR آن‌ها بگیرید", "qr.subtitle": "دوربین را به QR آن‌ها بگیرید",
"qr.scanning": "در حال خواندن…", "qr.scanning": "در حال خواندن…",
+16 -12
View File
@@ -93,9 +93,10 @@ export const DICTIONARY = {
"intro.createDesc": "Votre appareil génère les clés et une invitation à usage unique. Rien de tout cela ne passe par un serveur.", "intro.createDesc": "Votre appareil génère les clés et une invitation à usage unique. Rien de tout cela ne passe par un serveur.",
"intro.createCta": "Générer les clés et l'invitation", "intro.createCta": "Générer les clés et l'invitation",
"intro.joinTitle": "Rejoindre un canal", "intro.joinTitle": "Rejoindre un canal",
"intro.joinDesc": "Scannez le QR de votre correspondant avec l'appareil photo, ou collez son code d'invitation.",
"intro.scanTitle": "Scanner le QR avec la caméra", "intro.scanTitle": "Scanner le QR avec la caméra",
"intro.scanSubtitle": "Le plus rapide : visez l'écran de votre correspondant", "intro.scanSubtitle": "Le plus rapide : visez l'écran de votre correspondant",
"intro.orPasteCode": "ou collez le code", "intro.orPasteCode": "Ou collez le code",
"intro.pastePlaceholder": "Collez ici le code d'invitation…", "intro.pastePlaceholder": "Collez ici le code d'invitation…",
"intro.connect": "Se connecter", "intro.connect": "Se connecter",
"intro.connecting": "Traitement…", "intro.connecting": "Traitement…",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "Établir la connexion", "handshake.establish": "Établir la connexion",
"handshake.thenReceive": "Recevez ensuite la réponse que votre correspondant renvoie", "handshake.thenReceive": "Recevez ensuite la réponse que votre correspondant renvoie",
"handshake.pasteAnswerPlaceholder": "Collez le code de réponse…", "handshake.pasteAnswerPlaceholder": "Collez le code de réponse…",
"handshake.answerSentNote": "Envoyez cette réponse à la personne qui a créé le canal, puis patientez : la discussion s'ouvre dès qu'elle se connecte.", "handshake.answerSentNote": "En attente de la connexion du créateur. Le chat s'ouvrira automatiquement.",
"handshake.qrHint": "Gardez ceci ouvert jusqu'à ce que votre correspondant ait capturé le code.", "handshake.qrHint": "Gardez ceci ouvert jusqu'à ce que votre correspondant ait capturé le code.",
"handshake.qrHintFrames": "La poignée de main est répartie sur {frames} images : gardez ceci ouvert jusqu'à ce que votre correspondant les ait toutes capturées.", "handshake.qrHintFrames": "La poignée de main est répartie sur {frames} images : gardez ceci ouvert jusqu'à ce que votre correspondant les ait toutes capturées.",
"verify.title": "Vérification de sécurité",
"verify.desc": "Comparez ce code de sécurité avec votre correspondant par un autre canal (voix ou en personne), puis saisissez-le pour ouvrir la discussion.",
"verify.enterLabel": "Saisissez le code vérifié",
"verify.placeholder": "Tapez le code ici", "verify.placeholder": "Tapez le code ici",
"verify.confirm": "Confirmer le code", "verify.confirm": "Confirmer le code",
"verify.confirmed": "Confirmé", "verify.confirmed": "Confirmé",
"verify.mismatch": "Ne correspond pas",
"verify.yours": "Votre confirmation",
"verify.peer": "Confirmation du correspondant",
"verify.pending": "En attente", "verify.pending": "En attente",
"verify.waiting": "En attente du code…", "verify.waiting": "En attente du code…",
"verify.verified": "Canal vérifié", "sasv.title": "Vérifiez votre correspondant",
"verify.bothConfirmed": "Les deux parties ont confirmé. Ouverture de la discussion sécurisée…", "sasv.titleDone": "Correspondant vérifié",
"sasv.desc": "Lisez le code à voix haute par appel ou en personne. Ne continuez que si votre correspondant voit les mêmes chiffres.",
"sasv.descDone": "Vous avez tous les deux confirmé le même code. La connexion est authentifiée.",
"sasv.enter": "Saisissez le code",
"sasv.match": "Correspond",
"sasv.noMatch": "Ne correspond pas",
"sasv.you": "Vous",
"sasv.peer": "Correspondant",
"sasv.waitingPeer": "En attente du correspondant…",
"sasv.codesDiffer": "Les codes ne correspondent pas",
"sasv.opening": "Ouverture du chat sécurisé",
"pwa.bannerTitle": "Installer SecureBit.chat", "pwa.bannerTitle": "Installer SecureBit.chat",
"pwa.bannerDesc": "Profitez de l'expérience d'une application native, avec une sécurité renforcée", "pwa.bannerDesc": "Profitez de l'expérience d'une application native, avec une sécurité renforcée",
"pwa.install": "Installer", "pwa.install": "Installer",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "Scanner", "action.scan": "Scanner",
"action.downloadDesktop": "Télécharger l'application", "action.downloadDesktop": "Télécharger l'application",
"action.advancedSettings": "Réglages avancés", "action.advancedSettings": "Réglages avancés",
"cred.offerTag": "offre · ou copiez le texte",
"cred.answerTag": "réponse · ou copiez le texte",
"cred.reveal": "Cliquez pour afficher - gardez ce code privé", "cred.reveal": "Cliquez pour afficher - gardez ce code privé",
"cred.orCopy": "Ou copiez le code",
"qr.title": "Scanner le QR", "qr.title": "Scanner le QR",
"qr.subtitle": "Visez le QR de votre correspondant", "qr.subtitle": "Visez le QR de votre correspondant",
"qr.scanning": "Lecture…", "qr.scanning": "Lecture…",
+16 -12
View File
@@ -93,9 +93,10 @@ export const DICTIONARY = {
"intro.createDesc": "המכשיר שלכם יוצר את המפתחות והזמנה חד־פעמית. שום דבר לא נוגע בשרת.", "intro.createDesc": "המכשיר שלכם יוצר את המפתחות והזמנה חד־פעמית. שום דבר לא נוגע בשרת.",
"intro.createCta": "יצירת מפתחות והזמנה", "intro.createCta": "יצירת מפתחות והזמנה",
"intro.joinTitle": "הצטרפו לערוץ", "intro.joinTitle": "הצטרפו לערוץ",
"intro.joinDesc": "סרקו את קוד ה-QR של הצד השני במצלמה, או הדביקו את קוד ההזמנה.",
"intro.scanTitle": "סרקו QR עם המצלמה", "intro.scanTitle": "סרקו QR עם המצלמה",
"intro.scanSubtitle": "הכי מהיר - כוונו למסך של העמית", "intro.scanSubtitle": "הכי מהיר - כוונו למסך של העמית",
"intro.orPasteCode": "או הדביקו קוד", "intro.orPasteCode": "או הדביקו את הקוד",
"intro.pastePlaceholder": "הדביקו כאן את קוד ההזמנה…", "intro.pastePlaceholder": "הדביקו כאן את קוד ההזמנה…",
"intro.connect": "התחברו", "intro.connect": "התחברו",
"intro.connecting": "מעבד…", "intro.connecting": "מעבד…",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "יצירת חיבור", "handshake.establish": "יצירת חיבור",
"handshake.thenReceive": "ואז קבלו את התשובה שהעמית שולח בחזרה", "handshake.thenReceive": "ואז קבלו את התשובה שהעמית שולח בחזרה",
"handshake.pasteAnswerPlaceholder": "הדביקו את קוד התשובה של העמית…", "handshake.pasteAnswerPlaceholder": "הדביקו את קוד התשובה של העמית…",
"handshake.answerSentNote": "שלחו את התשובה ליוצר ואז המתינו - הצ'אט נפתח ברגע שהם מתחברים.", "handshake.answerSentNote": "ממתינים שהיוצר יתחבר. הצ'אט ייפתח אוטומטית.",
"handshake.qrHint": "השאירו פתוח עד שהעמית יקלוט את הקוד.", "handshake.qrHint": "השאירו פתוח עד שהעמית יקלוט את הקוד.",
"handshake.qrHintFrames": "לחיצת היד מפוצלת ל-{frames} פריימים - השאירו פתוח עד שהעמית יקלוט את כולם.", "handshake.qrHintFrames": "לחיצת היד מפוצלת ל-{frames} פריימים - השאירו פתוח עד שהעמית יקלוט את כולם.",
"verify.title": "אימות אבטחה",
"verify.desc": "השוו את קוד הבטיחות הזה עם העמית בערוץ נפרד (שיחה קולית / פנים אל פנים), ואז הקלידו אותו כדי לפתוח את הצ'אט.",
"verify.enterLabel": "הזינו את הקוד המאומת",
"verify.placeholder": "הקלידו כאן את הקוד", "verify.placeholder": "הקלידו כאן את הקוד",
"verify.confirm": "אשרו את הקוד", "verify.confirm": "אשרו את הקוד",
"verify.confirmed": "אושר", "verify.confirmed": "אושר",
"verify.mismatch": "לא תואמים",
"verify.yours": "האישור שלכם",
"verify.peer": "אישור העמית",
"verify.pending": "ממתין", "verify.pending": "ממתין",
"verify.waiting": "ממתין לקוד…", "verify.waiting": "ממתין לקוד…",
"verify.verified": "הערוץ אומת", "sasv.title": "אמתו את הצד השני",
"verify.bothConfirmed": "שני הצדדים אישרו. פותח את הצ'אט המאובטח…", "sasv.titleDone": "הצד השני אומת",
"sasv.desc": "הקריאו את הקוד בקול בשיחה או פנים אל פנים. המשיכו רק אם הצד השני רואה את אותן ספרות.",
"sasv.descDone": "שניכם אישרתם את אותו קוד. החיבור מאומת.",
"sasv.enter": "הזינו את הקוד",
"sasv.match": "תואם",
"sasv.noMatch": "לא תואם",
"sasv.you": "אתם",
"sasv.peer": "הצד השני",
"sasv.waitingPeer": "ממתינים לצד השני…",
"sasv.codesDiffer": "הקודים לא תואמים",
"sasv.opening": "פותחים צ'אט מאובטח",
"pwa.bannerTitle": "התקינו את SecureBit.chat", "pwa.bannerTitle": "התקינו את SecureBit.chat",
"pwa.bannerDesc": "קבלו חוויית אפליקציה מקורית עם אבטחה משופרת", "pwa.bannerDesc": "קבלו חוויית אפליקציה מקורית עם אבטחה משופרת",
"pwa.install": "התקנה", "pwa.install": "התקנה",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "סריקה", "action.scan": "סריקה",
"action.downloadDesktop": "הורידו את אפליקציית שולחן העבודה", "action.downloadDesktop": "הורידו את אפליקציית שולחן העבודה",
"action.advancedSettings": "הגדרות מתקדמות", "action.advancedSettings": "הגדרות מתקדמות",
"cred.offerTag": "הצעה · או העתיקו טקסט",
"cred.answerTag": "תשובה · או העתיקו טקסט",
"cred.reveal": "לחצו לחשיפה - שמרו את הקוד הזה פרטי", "cred.reveal": "לחצו לחשיפה - שמרו את הקוד הזה פרטי",
"cred.orCopy": "או העתיקו את הקוד",
"qr.title": "סריקת קוד QR", "qr.title": "סריקת קוד QR",
"qr.subtitle": "כוונו את המצלמה ל-QR שלהם", "qr.subtitle": "כוונו את המצלמה ל-QR שלהם",
"qr.scanning": "סורק…", "qr.scanning": "סורק…",
+16 -12
View File
@@ -93,9 +93,10 @@ export const DICTIONARY = {
"intro.createDesc": "आपका उपकरण कुंजियाँ और एक बार काम आने वाला निमंत्रण बनाता है। इनमें से कुछ भी किसी सर्वर तक नहीं जाता।", "intro.createDesc": "आपका उपकरण कुंजियाँ और एक बार काम आने वाला निमंत्रण बनाता है। इनमें से कुछ भी किसी सर्वर तक नहीं जाता।",
"intro.createCta": "कुंजियाँ और निमंत्रण बनाएँ", "intro.createCta": "कुंजियाँ और निमंत्रण बनाएँ",
"intro.joinTitle": "चैनल में शामिल हों", "intro.joinTitle": "चैनल में शामिल हों",
"intro.joinDesc": "कैमरे से अपने साथी का QR स्कैन करें, या उनका आमंत्रण कोड पेस्ट करें।",
"intro.scanTitle": "कैमरे से QR स्कैन करें", "intro.scanTitle": "कैमरे से QR स्कैन करें",
"intro.scanSubtitle": "सबसे तेज़ - सामने वाले की स्क्रीन पर कैमरा रखें", "intro.scanSubtitle": "सबसे तेज़ - सामने वाले की स्क्रीन पर कैमरा रखें",
"intro.orPasteCode": "या कोड चिपकाएँ", "intro.orPasteCode": "या कोड पेस्ट करें",
"intro.pastePlaceholder": "निमंत्रण कोड यहाँ चिपकाएँ…", "intro.pastePlaceholder": "निमंत्रण कोड यहाँ चिपकाएँ…",
"intro.connect": "जोड़ें", "intro.connect": "जोड़ें",
"intro.connecting": "प्रक्रिया जारी…", "intro.connecting": "प्रक्रिया जारी…",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "जुड़ाव बनाएँ", "handshake.establish": "जुड़ाव बनाएँ",
"handshake.thenReceive": "फिर सामने वाले का भेजा उत्तर लीजिए", "handshake.thenReceive": "फिर सामने वाले का भेजा उत्तर लीजिए",
"handshake.pasteAnswerPlaceholder": "सामने वाले का उत्तर कोड चिपकाएँ…", "handshake.pasteAnswerPlaceholder": "सामने वाले का उत्तर कोड चिपकाएँ…",
"handshake.answerSentNote": "यह उत्तर चैनल बनाने वाले को भेजिए और रुकिए - जैसे ही वे जुड़ेंगे, चैट खुल जाएगी।", "handshake.answerSentNote": "निर्माता के जुड़ने की प्रतीक्षा है। चैट अपने-आप खुल जाएगी।",
"handshake.qrHint": "जब तक सामने वाला कोड पढ़ न ले, इसे खुला रखिए।", "handshake.qrHint": "जब तक सामने वाला कोड पढ़ न ले, इसे खुला रखिए।",
"handshake.qrHintFrames": "हाथ मिलाना {frames} फ़्रेमों में बँटा है - जब तक सामने वाला सब पढ़ न ले, इसे खुला रखिए।", "handshake.qrHintFrames": "हाथ मिलाना {frames} फ़्रेमों में बँटा है - जब तक सामने वाला सब पढ़ न ले, इसे खुला रखिए।",
"verify.title": "सुरक्षा सत्यापन",
"verify.desc": "इस सुरक्षा कोड को किसी दूसरे रास्ते से (आवाज़ पर या आमने-सामने) मिलाइए, फिर उसे लिखकर चैट खोलिए।",
"verify.enterLabel": "मिलाया हुआ कोड लिखें",
"verify.placeholder": "कोड यहाँ लिखें", "verify.placeholder": "कोड यहाँ लिखें",
"verify.confirm": "कोड पक्का करें", "verify.confirm": "कोड पक्का करें",
"verify.confirmed": "पक्का हुआ", "verify.confirmed": "पक्का हुआ",
"verify.mismatch": "मेल नहीं खाता",
"verify.yours": "आपकी पुष्टि",
"verify.peer": "सामने वाले की पुष्टि",
"verify.pending": "प्रतीक्षा में", "verify.pending": "प्रतीक्षा में",
"verify.waiting": "कोड की प्रतीक्षा…", "verify.waiting": "कोड की प्रतीक्षा…",
"verify.verified": "चैनल सत्यापित", "sasv.title": "अपने साथी की पुष्टि करें",
"verify.bothConfirmed": "दोनों पक्षों ने पुष्टि कर दी। सुरक्षित चैट खुल रही है…", "sasv.titleDone": "साथी की पुष्टि हो गई",
"sasv.desc": "कॉल पर या आमने-सामने कोड ज़ोर से पढ़ें। तभी आगे बढ़ें जब आपके साथी को वही अंक दिखें।",
"sasv.descDone": "आप दोनों ने एक ही कोड की पुष्टि की। कनेक्शन प्रमाणित है।",
"sasv.enter": "कोड दर्ज करें",
"sasv.match": "मेल खाता है",
"sasv.noMatch": "मेल नहीं खाता",
"sasv.you": "आप",
"sasv.peer": "साथी",
"sasv.waitingPeer": "साथी की प्रतीक्षा…",
"sasv.codesDiffer": "कोड मेल नहीं खाते",
"sasv.opening": "सुरक्षित चैट खुल रही है",
"pwa.bannerTitle": "SecureBit.chat इंस्टॉल करें", "pwa.bannerTitle": "SecureBit.chat इंस्टॉल करें",
"pwa.bannerDesc": "बेहतर सुरक्षा के साथ मूल ऐप जैसा अनुभव पाइए", "pwa.bannerDesc": "बेहतर सुरक्षा के साथ मूल ऐप जैसा अनुभव पाइए",
"pwa.install": "इंस्टॉल करें", "pwa.install": "इंस्टॉल करें",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "स्कैन करें", "action.scan": "स्कैन करें",
"action.downloadDesktop": "डेस्कटॉप ऐप डाउनलोड करें", "action.downloadDesktop": "डेस्कटॉप ऐप डाउनलोड करें",
"action.advancedSettings": "उन्नत सेटिंग्स", "action.advancedSettings": "उन्नत सेटिंग्स",
"cred.offerTag": "निमंत्रण · या पाठ कॉपी करें",
"cred.answerTag": "उत्तर · या पाठ कॉपी करें",
"cred.reveal": "देखने के लिए दबाएँ - यह कोड किसी को न बताएँ", "cred.reveal": "देखने के लिए दबाएँ - यह कोड किसी को न बताएँ",
"cred.orCopy": "या कोड कॉपी करें",
"qr.title": "QR कोड स्कैन करें", "qr.title": "QR कोड स्कैन करें",
"qr.subtitle": "कैमरा सामने वाले के QR पर रखें", "qr.subtitle": "कैमरा सामने वाले के QR पर रखें",
"qr.scanning": "पढ़ा जा रहा है…", "qr.scanning": "पढ़ा जा रहा है…",
+15 -11
View File
@@ -93,6 +93,7 @@ export const DICTIONARY = {
"intro.createDesc": "여러분의 기기가 키와 일회용 초대장을 만듭니다. 그 어느 것도 서버를 거치지 않습니다.", "intro.createDesc": "여러분의 기기가 키와 일회용 초대장을 만듭니다. 그 어느 것도 서버를 거치지 않습니다.",
"intro.createCta": "키와 초대장 만들기", "intro.createCta": "키와 초대장 만들기",
"intro.joinTitle": "채널 참여하기", "intro.joinTitle": "채널 참여하기",
"intro.joinDesc": "카메라로 상대방의 QR을 스캔하거나 초대 코드를 붙여넣으세요.",
"intro.scanTitle": "카메라로 QR 스캔", "intro.scanTitle": "카메라로 QR 스캔",
"intro.scanSubtitle": "가장 빠릅니다 - 상대의 화면을 비추세요", "intro.scanSubtitle": "가장 빠릅니다 - 상대의 화면을 비추세요",
"intro.orPasteCode": "또는 코드 붙여넣기", "intro.orPasteCode": "또는 코드 붙여넣기",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "연결 맺기", "handshake.establish": "연결 맺기",
"handshake.thenReceive": "그다음 상대가 보내는 응답을 받으세요", "handshake.thenReceive": "그다음 상대가 보내는 응답을 받으세요",
"handshake.pasteAnswerPlaceholder": "상대의 응답 코드를 붙여넣으세요…", "handshake.pasteAnswerPlaceholder": "상대의 응답 코드를 붙여넣으세요…",
"handshake.answerSentNote": "이 응답을 만든 사람에게 보내고 기다리세요 - 상대가 연결되면 대화가 열립니다.", "handshake.answerSentNote": "생성자의 연결을 기다리는 중입니다. 채팅이 자동으로 열립니다.",
"handshake.qrHint": "상대가 코드를 다 읽을 때까지 이 화면을 열어 두세요.", "handshake.qrHint": "상대가 코드를 다 읽을 때까지 이 화면을 열어 두세요.",
"handshake.qrHintFrames": "악수가 {frames}개 화면으로 나뉘어 있습니다 - 상대가 전부 읽을 때까지 열어 두세요.", "handshake.qrHintFrames": "악수가 {frames}개 화면으로 나뉘어 있습니다 - 상대가 전부 읽을 때까지 열어 두세요.",
"verify.title": "보안 확인",
"verify.desc": "다른 경로(통화나 대면)로 상대와 이 안전 코드를 대조한 뒤, 입력해서 대화를 여세요.",
"verify.enterLabel": "대조한 코드를 입력하세요",
"verify.placeholder": "여기에 코드 입력", "verify.placeholder": "여기에 코드 입력",
"verify.confirm": "코드 확인", "verify.confirm": "코드 확인",
"verify.confirmed": "확인됨", "verify.confirmed": "확인됨",
"verify.mismatch": "일치하지 않음",
"verify.yours": "내 확인",
"verify.peer": "상대의 확인",
"verify.pending": "대기 중", "verify.pending": "대기 중",
"verify.waiting": "코드 대기 중…", "verify.waiting": "코드 대기 중…",
"verify.verified": "채널 확인 완료", "sasv.title": "상대방 확인",
"verify.bothConfirmed": "양쪽 모두 확인했습니다. 안전한 대화를 여는 중…", "sasv.titleDone": "상대방 확인 완료",
"sasv.desc": "통화나 대면으로 코드를 소리 내어 읽어 주세요. 상대방이 같은 숫자를 볼 때만 계속하세요.",
"sasv.descDone": "두 사람 모두 같은 코드를 확인했습니다. 연결이 인증되었습니다.",
"sasv.enter": "코드 입력",
"sasv.match": "일치",
"sasv.noMatch": "일치하지 않음",
"sasv.you": "나",
"sasv.peer": "상대방",
"sasv.waitingPeer": "상대방을 기다리는 중…",
"sasv.codesDiffer": "코드가 일치하지 않음",
"sasv.opening": "보안 채팅을 여는 중",
"pwa.bannerTitle": "SecureBit.chat 설치", "pwa.bannerTitle": "SecureBit.chat 설치",
"pwa.bannerDesc": "보안이 강화된 네이티브 앱 경험을 누리세요", "pwa.bannerDesc": "보안이 강화된 네이티브 앱 경험을 누리세요",
"pwa.install": "설치", "pwa.install": "설치",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "스캔", "action.scan": "스캔",
"action.downloadDesktop": "데스크톱 앱 내려받기", "action.downloadDesktop": "데스크톱 앱 내려받기",
"action.advancedSettings": "고급 설정", "action.advancedSettings": "고급 설정",
"cred.offerTag": "초대 · 또는 텍스트 복사",
"cred.answerTag": "응답 · 또는 텍스트 복사",
"cred.reveal": "눌러서 보기 - 이 코드는 남에게 알리지 마세요", "cred.reveal": "눌러서 보기 - 이 코드는 남에게 알리지 마세요",
"cred.orCopy": "또는 코드 복사",
"qr.title": "QR 코드 스캔", "qr.title": "QR 코드 스캔",
"qr.subtitle": "상대의 QR에 카메라를 맞추세요", "qr.subtitle": "상대의 QR에 카메라를 맞추세요",
"qr.scanning": "읽는 중…", "qr.scanning": "읽는 중…",
+16 -12
View File
@@ -93,9 +93,10 @@ export const DICTIONARY = {
"intro.createDesc": "Ваше устройство создаёт ключи и одноразовое приглашение. Ничто из этого не попадает на сервер.", "intro.createDesc": "Ваше устройство создаёт ключи и одноразовое приглашение. Ничто из этого не попадает на сервер.",
"intro.createCta": "Создать ключи и приглашение", "intro.createCta": "Создать ключи и приглашение",
"intro.joinTitle": "Присоединиться к каналу", "intro.joinTitle": "Присоединиться к каналу",
"intro.joinDesc": "Отсканируйте QR собеседника камерой или вставьте его код приглашения.",
"intro.scanTitle": "Сканировать QR камерой", "intro.scanTitle": "Сканировать QR камерой",
"intro.scanSubtitle": "Быстрее всего - наведите на экран собеседника", "intro.scanSubtitle": "Быстрее всего - наведите на экран собеседника",
"intro.orPasteCode": "или вставьте код", "intro.orPasteCode": "Или вставьте код",
"intro.pastePlaceholder": "Вставьте код приглашения сюда…", "intro.pastePlaceholder": "Вставьте код приглашения сюда…",
"intro.connect": "Соединиться", "intro.connect": "Соединиться",
"intro.connecting": "Обработка…", "intro.connecting": "Обработка…",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "Установить соединение", "handshake.establish": "Установить соединение",
"handshake.thenReceive": "Затем получите ответ, который пришлёт собеседник", "handshake.thenReceive": "Затем получите ответ, который пришлёт собеседник",
"handshake.pasteAnswerPlaceholder": "Вставьте код ответа собеседника…", "handshake.pasteAnswerPlaceholder": "Вставьте код ответа собеседника…",
"handshake.answerSentNote": "Отправьте этот ответ создателю и подождите - чат откроется, как только он подключится.", "handshake.answerSentNote": "Ждём, пока создатель подключится. Чат откроется автоматически.",
"handshake.qrHint": "Держите это открытым, пока собеседник не считает код.", "handshake.qrHint": "Держите это открытым, пока собеседник не считает код.",
"handshake.qrHintFrames": "Рукопожатие разбито на {frames} кадров - держите это открытым, пока собеседник не считает все.", "handshake.qrHintFrames": "Рукопожатие разбито на {frames} кадров - держите это открытым, пока собеседник не считает все.",
"verify.title": "Проверка безопасности",
"verify.desc": "Сверьте этот код безопасности с собеседником по другому каналу (голосом или лично), затем введите его, чтобы открыть чат.",
"verify.enterLabel": "Введите сверенный код",
"verify.placeholder": "Введите код здесь", "verify.placeholder": "Введите код здесь",
"verify.confirm": "Подтвердить код", "verify.confirm": "Подтвердить код",
"verify.confirmed": "Подтверждено", "verify.confirmed": "Подтверждено",
"verify.mismatch": "Не совпадают",
"verify.yours": "Ваше подтверждение",
"verify.peer": "Подтверждение собеседника",
"verify.pending": "Ожидается", "verify.pending": "Ожидается",
"verify.waiting": "Ожидание кода…", "verify.waiting": "Ожидание кода…",
"verify.verified": "Канал проверен", "sasv.title": "Проверьте собеседника",
"verify.bothConfirmed": "Обе стороны подтвердили. Открываем защищённый чат…", "sasv.titleDone": "Собеседник проверен",
"sasv.desc": "Прочитайте код вслух по звонку или при встрече. Продолжайте, только если собеседник видит те же цифры.",
"sasv.descDone": "Вы оба подтвердили один и тот же код. Соединение подлинное.",
"sasv.enter": "Введите код",
"sasv.match": "Совпадает",
"sasv.noMatch": "Не совпадает",
"sasv.you": "Вы",
"sasv.peer": "Собеседник",
"sasv.waitingPeer": "Ждём собеседника…",
"sasv.codesDiffer": "Коды не совпадают",
"sasv.opening": "Открываем защищённый чат",
"pwa.bannerTitle": "Установите SecureBit.chat", "pwa.bannerTitle": "Установите SecureBit.chat",
"pwa.bannerDesc": "Опыт полноценного приложения с усиленной защитой", "pwa.bannerDesc": "Опыт полноценного приложения с усиленной защитой",
"pwa.install": "Установить", "pwa.install": "Установить",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "Сканировать", "action.scan": "Сканировать",
"action.downloadDesktop": "Скачать приложение", "action.downloadDesktop": "Скачать приложение",
"action.advancedSettings": "Дополнительные настройки", "action.advancedSettings": "Дополнительные настройки",
"cred.offerTag": "предложение · или скопируйте текст",
"cred.answerTag": "ответ · или скопируйте текст",
"cred.reveal": "Нажмите, чтобы показать - держите этот код в тайне", "cred.reveal": "Нажмите, чтобы показать - держите этот код в тайне",
"cred.orCopy": "Или скопируйте код",
"qr.title": "Сканировать QR-код", "qr.title": "Сканировать QR-код",
"qr.subtitle": "Наведите камеру на их QR-код", "qr.subtitle": "Наведите камеру на их QR-код",
"qr.scanning": "Сканирование…", "qr.scanning": "Сканирование…",
+16 -12
View File
@@ -93,9 +93,10 @@ export const DICTIONARY = {
"intro.createDesc": "Ваш пристрій створює ключі та одноразове запрошення. Ніщо з цього не потрапляє на сервер.", "intro.createDesc": "Ваш пристрій створює ключі та одноразове запрошення. Ніщо з цього не потрапляє на сервер.",
"intro.createCta": "Створити ключі та запрошення", "intro.createCta": "Створити ключі та запрошення",
"intro.joinTitle": "Приєднатися до каналу", "intro.joinTitle": "Приєднатися до каналу",
"intro.joinDesc": "Відскануйте QR співрозмовника камерою або вставте його код запрошення.",
"intro.scanTitle": "Сканувати QR камерою", "intro.scanTitle": "Сканувати QR камерою",
"intro.scanSubtitle": "Найшвидше - наведіть на екран співрозмовника", "intro.scanSubtitle": "Найшвидше - наведіть на екран співрозмовника",
"intro.orPasteCode": "або вставте код", "intro.orPasteCode": "Або вставте код",
"intro.pastePlaceholder": "Вставте код запрошення сюди…", "intro.pastePlaceholder": "Вставте код запрошення сюди…",
"intro.connect": "З'єднатися", "intro.connect": "З'єднатися",
"intro.connecting": "Обробка…", "intro.connecting": "Обробка…",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "Встановити з'єднання", "handshake.establish": "Встановити з'єднання",
"handshake.thenReceive": "Потім отримайте відповідь, яку надішле співрозмовник", "handshake.thenReceive": "Потім отримайте відповідь, яку надішле співрозмовник",
"handshake.pasteAnswerPlaceholder": "Вставте код відповіді співрозмовника…", "handshake.pasteAnswerPlaceholder": "Вставте код відповіді співрозмовника…",
"handshake.answerSentNote": "Надішліть цю відповідь створювачу й зачекайте - чат відкриється, щойно він приєднається.", "handshake.answerSentNote": "Чекаємо, поки творець під'єднається. Чат відкриється автоматично.",
"handshake.qrHint": "Тримайте це відкритим, доки співрозмовник не зчитає код.", "handshake.qrHint": "Тримайте це відкритим, доки співрозмовник не зчитає код.",
"handshake.qrHintFrames": "Рукостискання розбите на {frames} кадрів - тримайте це відкритим, доки співрозмовник не зчитає всі.", "handshake.qrHintFrames": "Рукостискання розбите на {frames} кадрів - тримайте це відкритим, доки співрозмовник не зчитає всі.",
"verify.title": "Перевірка безпеки",
"verify.desc": "Звірте цей код безпеки зі співрозмовником іншим каналом (голосом або особисто), потім введіть його, щоб відкрити чат.",
"verify.enterLabel": "Введіть звірений код",
"verify.placeholder": "Введіть код тут", "verify.placeholder": "Введіть код тут",
"verify.confirm": "Підтвердити код", "verify.confirm": "Підтвердити код",
"verify.confirmed": "Підтверджено", "verify.confirmed": "Підтверджено",
"verify.mismatch": "Не збігаються",
"verify.yours": "Ваше підтвердження",
"verify.peer": "Підтвердження співрозмовника",
"verify.pending": "Очікується", "verify.pending": "Очікується",
"verify.waiting": "Очікування коду…", "verify.waiting": "Очікування коду…",
"verify.verified": "Канал перевірено", "sasv.title": "Перевірте співрозмовника",
"verify.bothConfirmed": "Обидві сторони підтвердили. Відкриваємо захищений чат…", "sasv.titleDone": "Співрозмовника перевірено",
"sasv.desc": "Прочитайте код уголос під час дзвінка або зустрічі. Продовжуйте, лише якщо співрозмовник бачить ті самі цифри.",
"sasv.descDone": "Ви обидва підтвердили той самий код. З'єднання справжнє.",
"sasv.enter": "Введіть код",
"sasv.match": "Збігається",
"sasv.noMatch": "Не збігається",
"sasv.you": "Ви",
"sasv.peer": "Співрозмовник",
"sasv.waitingPeer": "Чекаємо на співрозмовника…",
"sasv.codesDiffer": "Коди не збігаються",
"sasv.opening": "Відкриваємо захищений чат",
"pwa.bannerTitle": "Встановіть SecureBit.chat", "pwa.bannerTitle": "Встановіть SecureBit.chat",
"pwa.bannerDesc": "Досвід власного застосунку з посиленим захистом", "pwa.bannerDesc": "Досвід власного застосунку з посиленим захистом",
"pwa.install": "Встановити", "pwa.install": "Встановити",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "Сканувати", "action.scan": "Сканувати",
"action.downloadDesktop": "Завантажити застосунок", "action.downloadDesktop": "Завантажити застосунок",
"action.advancedSettings": "Додаткові налаштування", "action.advancedSettings": "Додаткові налаштування",
"cred.offerTag": "пропозиція · або скопіюйте текст",
"cred.answerTag": "відповідь · або скопіюйте текст",
"cred.reveal": "Натисніть, щоб показати - тримайте цей код у таємниці", "cred.reveal": "Натисніть, щоб показати - тримайте цей код у таємниці",
"cred.orCopy": "Або скопіюйте код",
"qr.title": "Сканувати QR-код", "qr.title": "Сканувати QR-код",
"qr.subtitle": "Наведіть камеру на їхній QR-код", "qr.subtitle": "Наведіть камеру на їхній QR-код",
"qr.scanning": "Сканування…", "qr.scanning": "Сканування…",
+15 -11
View File
@@ -93,6 +93,7 @@ export const DICTIONARY = {
"intro.createDesc": "آپ کا آلہ کلیدیں اور ایک بار کی دعوت بناتا ہے۔ کچھ بھی سرور کو نہیں چھوتا۔", "intro.createDesc": "آپ کا آلہ کلیدیں اور ایک بار کی دعوت بناتا ہے۔ کچھ بھی سرور کو نہیں چھوتا۔",
"intro.createCta": "کلیدیں اور دعوت بنائیں", "intro.createCta": "کلیدیں اور دعوت بنائیں",
"intro.joinTitle": "چینل میں شامل ہوں", "intro.joinTitle": "چینل میں شامل ہوں",
"intro.joinDesc": "کیمرے سے اپنے ساتھی کا QR اسکین کریں، یا ان کا دعوتی کوڈ چسپاں کریں۔",
"intro.scanTitle": "کیمرے سے QR پڑھیں", "intro.scanTitle": "کیمرے سے QR پڑھیں",
"intro.scanSubtitle": "سب سے تیز - ساتھی کی سکرین کی طرف کریں", "intro.scanSubtitle": "سب سے تیز - ساتھی کی سکرین کی طرف کریں",
"intro.orPasteCode": "یا کوڈ چسپاں کریں", "intro.orPasteCode": "یا کوڈ چسپاں کریں",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "اتصال قائم کریں", "handshake.establish": "اتصال قائم کریں",
"handshake.thenReceive": "پھر وہ جواب وصول کریں جو آپ کا ساتھی واپس بھیجتا ہے", "handshake.thenReceive": "پھر وہ جواب وصول کریں جو آپ کا ساتھی واپس بھیجتا ہے",
"handshake.pasteAnswerPlaceholder": "ساتھی کا جوابی کوڈ چسپاں کریں…", "handshake.pasteAnswerPlaceholder": "ساتھی کا جوابی کوڈ چسپاں کریں…",
"handshake.answerSentNote": "یہ جواب بنانے والے کو بھیجیں، پھر انتظار کریں - ان کے جڑتے ہی گفتگو کھل جائے گی۔", "handshake.answerSentNote": "تخلیق کار کے جڑنے کا انتظار ہے۔ چیٹ خود بخود کھل جائے گی۔",
"handshake.qrHint": "یہ اس وقت تک کھلا رکھیں جب تک ساتھی کوڈ نہ پکڑ لے۔", "handshake.qrHint": "یہ اس وقت تک کھلا رکھیں جب تک ساتھی کوڈ نہ پکڑ لے۔",
"handshake.qrHintFrames": "مصافحہ {frames} فریموں میں بٹا ہے - یہ اس وقت تک کھلا رکھیں جب تک ساتھی سب نہ پکڑ لے۔", "handshake.qrHintFrames": "مصافحہ {frames} فریموں میں بٹا ہے - یہ اس وقت تک کھلا رکھیں جب تک ساتھی سب نہ پکڑ لے۔",
"verify.title": "حفاظتی تصدیق",
"verify.desc": "یہ حفاظتی کوڈ کسی الگ ذریعے (آواز / بالمشافہ) سے اپنے ساتھی کے ساتھ ملائیں، پھر گفتگو کھولنے کے لیے لکھیں۔",
"verify.enterLabel": "تصدیق شدہ کوڈ درج کریں",
"verify.placeholder": "کوڈ یہاں لکھیں", "verify.placeholder": "کوڈ یہاں لکھیں",
"verify.confirm": "کوڈ کی تصدیق", "verify.confirm": "کوڈ کی تصدیق",
"verify.confirmed": "تصدیق ہو گئی", "verify.confirmed": "تصدیق ہو گئی",
"verify.mismatch": "مطابقت نہیں",
"verify.yours": "آپ کی تصدیق",
"verify.peer": "ساتھی کی تصدیق",
"verify.pending": "زیرِ التوا", "verify.pending": "زیرِ التوا",
"verify.waiting": "کوڈ کا انتظار…", "verify.waiting": "کوڈ کا انتظار…",
"verify.verified": "چینل تصدیق شدہ", "sasv.title": "اپنے ساتھی کی تصدیق کریں",
"verify.bothConfirmed": "دونوں فریقوں نے تصدیق کی۔ محفوظ گفتگو کھولی جا رہی ہے…", "sasv.titleDone": "ساتھی کی تصدیق ہو گئی",
"sasv.desc": "کال پر یا آمنے سامنے کوڈ بلند آواز سے پڑھیں۔ صرف تب آگے بڑھیں جب آپ کے ساتھی کو وہی ہندسے نظر آئیں۔",
"sasv.descDone": "آپ دونوں نے ایک ہی کوڈ کی تصدیق کی۔ کنکشن مستند ہے۔",
"sasv.enter": "کوڈ درج کریں",
"sasv.match": "مماثل ہے",
"sasv.noMatch": "مماثل نہیں",
"sasv.you": "آپ",
"sasv.peer": "ساتھی",
"sasv.waitingPeer": "ساتھی کا انتظار…",
"sasv.codesDiffer": "کوڈ مماثل نہیں",
"sasv.opening": "محفوظ چیٹ کھل رہی ہے",
"pwa.bannerTitle": "‏SecureBit.chat نصب کریں", "pwa.bannerTitle": "‏SecureBit.chat نصب کریں",
"pwa.bannerDesc": "بہتر سلامتی کے ساتھ مقامی ایپ کا تجربہ حاصل کریں", "pwa.bannerDesc": "بہتر سلامتی کے ساتھ مقامی ایپ کا تجربہ حاصل کریں",
"pwa.install": "نصب", "pwa.install": "نصب",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "پڑھیں", "action.scan": "پڑھیں",
"action.downloadDesktop": "ڈیسک ٹاپ ایپ ڈاؤن لوڈ کریں", "action.downloadDesktop": "ڈیسک ٹاپ ایپ ڈاؤن لوڈ کریں",
"action.advancedSettings": "اعلیٰ ترتیبات", "action.advancedSettings": "اعلیٰ ترتیبات",
"cred.offerTag": "پیشکش · یا متن نقل کریں",
"cred.answerTag": "جواب · یا متن نقل کریں",
"cred.reveal": "دکھانے کے لیے دبائیں - یہ کوڈ نجی رکھیں", "cred.reveal": "دکھانے کے لیے دبائیں - یہ کوڈ نجی رکھیں",
"cred.orCopy": "یا کوڈ نقل کریں",
"qr.title": "‏QR کوڈ پڑھیں", "qr.title": "‏QR کوڈ پڑھیں",
"qr.subtitle": "اپنا کیمرہ ان کے QR کی طرف کریں", "qr.subtitle": "اپنا کیمرہ ان کے QR کی طرف کریں",
"qr.scanning": "پڑھا جا رہا ہے…", "qr.scanning": "پڑھا جا رہا ہے…",
+15 -11
View File
@@ -93,6 +93,7 @@ export const DICTIONARY = {
"intro.createDesc": "你的设备会生成密钥和一次性邀请。这些都不会经过任何服务器。", "intro.createDesc": "你的设备会生成密钥和一次性邀请。这些都不会经过任何服务器。",
"intro.createCta": "生成密钥与邀请", "intro.createCta": "生成密钥与邀请",
"intro.joinTitle": "加入通道", "intro.joinTitle": "加入通道",
"intro.joinDesc": "用相机扫描对方的二维码,或粘贴对方的邀请代码。",
"intro.scanTitle": "用摄像头扫描二维码", "intro.scanTitle": "用摄像头扫描二维码",
"intro.scanSubtitle": "最快的方式 —— 对准对方的屏幕", "intro.scanSubtitle": "最快的方式 —— 对准对方的屏幕",
"intro.orPasteCode": "或粘贴代码", "intro.orPasteCode": "或粘贴代码",
@@ -271,22 +272,26 @@ export const DICTIONARY = {
"handshake.establish": "建立连接", "handshake.establish": "建立连接",
"handshake.thenReceive": "然后接收对方发回的应答", "handshake.thenReceive": "然后接收对方发回的应答",
"handshake.pasteAnswerPlaceholder": "粘贴对方的应答码…", "handshake.pasteAnswerPlaceholder": "粘贴对方的应答码…",
"handshake.answerSentNote": "把这段应答发给创建者,然后等待 —— 对方一连上,聊天就会打开。", "handshake.answerSentNote": "正在等待创建者连接。聊天将自动打开。",
"handshake.qrHint": "在对方读取完代码之前,请保持此界面打开。", "handshake.qrHint": "在对方读取完代码之前,请保持此界面打开。",
"handshake.qrHintFrames": "握手被拆成 {frames} 帧 —— 在对方读完全部帧之前,请保持此界面打开。", "handshake.qrHintFrames": "握手被拆成 {frames} 帧 —— 在对方读完全部帧之前,请保持此界面打开。",
"verify.title": "安全校验",
"verify.desc": "通过另一条渠道(通话或当面)与对方核对这个安全码,然后输入它以解锁聊天。",
"verify.enterLabel": "输入核对过的安全码",
"verify.placeholder": "在此输入安全码", "verify.placeholder": "在此输入安全码",
"verify.confirm": "确认安全码", "verify.confirm": "确认安全码",
"verify.confirmed": "已确认", "verify.confirmed": "已确认",
"verify.mismatch": "不一致",
"verify.yours": "你的确认",
"verify.peer": "对方的确认",
"verify.pending": "等待中", "verify.pending": "等待中",
"verify.waiting": "等待安全码…", "verify.waiting": "等待安全码…",
"verify.verified": "通道已校验", "sasv.title": "验证对方",
"verify.bothConfirmed": "双方均已确认。正在打开安全聊天…", "sasv.titleDone": "对方已验证",
"sasv.desc": "通过通话或当面大声读出代码。只有对方看到相同的数字时才继续。",
"sasv.descDone": "你们双方确认了相同的代码。连接已通过认证。",
"sasv.enter": "输入代码",
"sasv.match": "一致",
"sasv.noMatch": "不一致",
"sasv.you": "你",
"sasv.peer": "对方",
"sasv.waitingPeer": "正在等待对方…",
"sasv.codesDiffer": "代码不一致",
"sasv.opening": "正在打开安全聊天",
"pwa.bannerTitle": "安装 SecureBit.chat", "pwa.bannerTitle": "安装 SecureBit.chat",
"pwa.bannerDesc": "获得原生应用般的体验,安全性更进一步", "pwa.bannerDesc": "获得原生应用般的体验,安全性更进一步",
"pwa.install": "安装", "pwa.install": "安装",
@@ -321,9 +326,8 @@ export const DICTIONARY = {
"action.scan": "扫描", "action.scan": "扫描",
"action.downloadDesktop": "下载桌面应用", "action.downloadDesktop": "下载桌面应用",
"action.advancedSettings": "高级设置", "action.advancedSettings": "高级设置",
"cred.offerTag": "邀请 · 或复制文本",
"cred.answerTag": "应答 · 或复制文本",
"cred.reveal": "点击显示 - 请勿泄露此代码", "cred.reveal": "点击显示 - 请勿泄露此代码",
"cred.orCopy": "或复制代码",
"qr.title": "扫描二维码", "qr.title": "扫描二维码",
"qr.subtitle": "把摄像头对准对方的二维码", "qr.subtitle": "把摄像头对准对方的二维码",
"qr.scanning": "扫描中…", "qr.scanning": "扫描中…",
+1
View File
@@ -798,6 +798,7 @@ button i {
.sb-start .sb-sc::-webkit-scrollbar-thumb { background: rgba(var(--sb-ink), 0.1); border-radius: 99px; } .sb-start .sb-sc::-webkit-scrollbar-thumb { background: rgba(var(--sb-ink), 0.1); border-radius: 99px; }
.sb-seg-btn { transition: color .2s; } .sb-seg-btn { transition: color .2s; }
.sb-soft-btn { transition: all .15s; } .sb-soft-btn { transition: all .15s; }
.sb-qr-row:hover { background: rgba(var(--sb-ink), 0.025) !important; }
.sb-soft-btn:hover { border-color: rgba(var(--sb-ink), 0.18) !important; color: var(--sb-text-2) !important; } .sb-soft-btn:hover { border-color: rgba(var(--sb-ink), 0.18) !important; color: var(--sb-text-2) !important; }
.sb-scan-btn:hover { border-color: rgba(var(--sb-green-rgb), 0.5) !important; background: rgba(var(--sb-green-rgb), 0.1) !important; } .sb-scan-btn:hover { border-color: rgba(var(--sb-green-rgb), 0.5) !important; background: rgba(var(--sb-green-rgb), 0.1) !important; }
.sb-gen-btn:hover { background: var(--sb-orange-hi) !important; } .sb-gen-btn:hover { background: var(--sb-orange-hi) !important; }
+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 = '1790225533889'; const SW_BUILD_VERSION = '1790540575787';
// 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
+7
View File
@@ -176,6 +176,13 @@ function sources() {
assert.ok(app.slice(cells, cells + 60).includes("dir: 'ltr'"), assert.ok(app.slice(cells, cells + 60).includes("dir: 'ltr'"),
'the safety-code tiles sit in a flex row, so they must be pinned left-to-right'); 'the safety-code tiles sit in a flex row, so they must be pinned left-to-right');
// The same code printed large above the tiles, in two groups side by side — another
// flex row, so in a right-to-left locale the groups would swap places.
const big = app.indexOf("key: 'code', style: { padding: '20px 0 22px'");
assert.notEqual(big, -1, 'the large safety-code display is gone — this guard now protects nothing');
assert.ok(app.slice(big, big + 400).includes("dir: 'ltr'"),
'the large safety-code display must be pinned left-to-right');
// The group code, wherever it is put on screen. A plain `group.sasCode` also appears // The group code, wherever it is put on screen. A plain `group.sasCode` also appears
// in guard conditions, which render nothing — so the window looks both ways and only // in guard conditions, which render nothing — so the window looks both ways and only
// the sites that are actually inside an element have to carry the direction. // the sites that are actually inside an element have to carry the direction.
+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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/uk.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/ur.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></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=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script src="/src/scripts/theme-boot.js?v=1790540575787"></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=1790225533889"></script> <script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></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=1790225533889"> <link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
<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=1790225533889"> <link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/config/ice-servers.js?v=1790540575787"></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=1790225533889"> <link rel="stylesheet" href="/assets/app.css?v=1790540575787">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889"> <link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
<!-- 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=1790225533889"></script> <script defer src="/src/scripts/load-async-css.js?v=1790540575787"></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=1790225533889"></script> <script defer src="/src/scripts/fa-check.js?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/src/i18n/dict/zh.js?v=1790540575787"></script>
<!-- Update Manager - система принудительного обновления --> <!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script> <script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script> <script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></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=1790225533889"></script> <script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
<script type="module" src="/dist/app.js?v=1790225533889"></script> <script type="module" src="/dist/app.js?v=1790540575787"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script> <script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script> <script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script> <script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
</body> </body>
</html> </html>