v6.8.12: new design for exchanging invitations
This commit is contained in:
1 parent
cd9d78d398
commit
b0444a52c6
57 files changed
+919
-693
No files matched your search
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
[](LICENSE)
|
||||||
[](CHANGELOG.md)
|
[](CHANGELOG.md)
|
||||||
[](https://snapcraft.io/securebit-chat)
|
[](https://snapcraft.io/securebit-chat)
|
||||||
[](#install-as-an-app)
|
[](#install-as-an-app)
|
||||||
[](#security-model)
|
[](#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 |
|
||||||
| :---: | :---: |
|
| :---: | :---: |
|
||||||
|  |  |
|
|  |  |
|
||||||
| 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
@@ -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
@@ -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
@@ -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>
|
||||||
Vendored
+3
-3
@@ -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,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").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,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").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
|
||||||
/**
|
/**
|
||||||
|
|||||||
Vendored
+2
-2
@@ -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
@@ -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,$=>({"&":"&","<":"<",">":">",'"':"""})[$]),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=>({"&":"&","<":"<",">":">",'"':"""})[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
|
||||||
Vendored
+3
-3
@@ -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
@@ -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.
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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": "扫描中…",
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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": "扫描中…",
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||||
Reference in new issue
Block a user