v6.8.5: the relay password is no longer built into the app
CodeQL Analysis / Analyze CodeQL (push) Canceled after 0s
Deploy Application / deploy (push) Canceled after 0s
Mirror to Codeberg / mirror (push) Canceled after 0s
Mirror to PrivacyGuides / mirror (push) Canceled after 0s

This commit is contained in:
lockbitchat
2026-09-23 20:10:03 -04:00
parent f39e9022b7
commit 02380a7ecd
28 changed files with 365 additions and 345 deletions
+9 -14
View File
@@ -1,21 +1,16 @@
# Changelog
## v6.8.4 — The relay password now changes every day
## v6.8.5 — The relay password is no longer built into the app
When two people cannot connect directly, the app routes the call through our own
relay server. Until now every copy of the app carried the same password for that
server, valid for years, and anyone could copy it and push their own traffic
through it. The site now hands each app a fresh password that runs out after a
day, and only gives them out at a limited pace. Nothing changes for you: the app
picks up a new one on its own, including during long conversations.
When two people cannot connect directly, calls and chats go through our relay
server. Until now every copy of the app carried the same password for it, valid
for years, so anyone could copy it and send their own traffic through our server.
That password is gone. The app now asks the site for a new one that works for a
day and renews it by itself, including during long conversations. The site hands
them out only at a limited pace. You do not need to do anything.
## v6.8.3 — Private project files are no longer published with the site
A few files meant only for the developer's own machine — local editor settings and
tool notes — were being uploaded along with the website and could be opened by anyone
who knew the address. They held no passwords or keys, but they had no business being
public. They are now left out of every deploy, along with anything else kept private
in the project.
A few files meant only for the developer's own computer were being published
along with the site. They held no passwords, but they are no longer published.
## v6.8.2 — The keyboard no longer pushes the chat off the screen
+1 -1
View File
@@ -9,7 +9,7 @@
No accounts. No servers storing your messages. No installation required.
[![License: MIT](https://img.shields.io/badge/License-MIT-f0892a.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-6.8.4-3ecf8e.svg)](CHANGELOG.md)
[![Version](https://img.shields.io/badge/version-6.8.5-3ecf8e.svg)](CHANGELOG.md)
[![Get it from the Snap Store](https://snapcraft.io/securebit-chat/badge.svg)](https://snapcraft.io/securebit-chat)
[![PWA](https://img.shields.io/badge/PWA-installable-3ecf8e.svg)](#install-as-an-app)
[![Encryption](https://img.shields.io/badge/crypto-ECDH%20P--384%20%C2%B7%20AES--256--GCM-blue.svg)](#security-model)
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/ar/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/ar.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/ar.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+6 -34
View File
@@ -1,5 +1,5 @@
// Production ICE override baked into the Fly.io image (no secrets — public STUN
// only). The Dockerfile copies this to config/ice-servers.js, which is otherwise
// only; the relay is added at runtime with a short-lived password). The Dockerfile copies this to config/ice-servers.js, which is otherwise
// git-ignored. Users who want a TURN relay can add one via "Advanced network
// settings"; to ship an operator TURN here, add it below (TURN credentials are
// visible to every browser, so rotate them if exposed).
@@ -12,37 +12,9 @@ window.SECUREBIT_ICE_SERVERS = [
// hostnames inside their WebRTC layer even though normal page DNS works, so they
// gather zero srflx/relay candidates and can't connect. Reaching the server by IP
// bypasses that. Harmless to other browsers.
{ urls: 'stun:144.172.96.126:3478' },
{
// SecureBit self-hosted coturn relay (turn.securebit.chat) — used only when
// direct P2P fails (strict NAT / STUN limits). Media stays E2E-encrypted over
// DTLS; the relay forwards only ciphertext. Multiple transports are offered so
// every browser finds one that works:
// - turn:3478?transport=udp — plain UDP, the most universally compatible
// transport (notably the one Safari reliably gathers relay candidates on).
// - turn:3478?transport=tcp — TCP fallback when UDP is blocked.
// - turns:443?transport=tcp — TURN over TLS on 443, bypasses restrictive
// firewalls that only allow HTTPS.
//
// The credential below is a long-lived token derived from the server's TURN
// REST API secret (static-auth-secret) — username is "<expiry-unix-ts>:label".
// Expiry 2147483647 = 2038-01-19 (max value coturn accepts; it parses the
// timestamp as int32). The master secret itself never leaves the server; only
// this derived username/credential pair is public (by design, like any
// browser-side TURN credential). To revoke/renew, rotate the server's
// static-auth-secret and re-issue with generate_turn_credentials.py.
urls: [
'turn:turn.securebit.chat:3478?transport=udp',
'turn:turn.securebit.chat:3478?transport=tcp',
// Raw-IP TURN relay — the DNS-bypass fallback that lets Safari (and any client
// whose WebRTC layer can't resolve the hostname) obtain a relay candidate.
// Plain turn: (no TLS) so no cert/hostname check is needed; the REST-API
// credential is host-independent, and relayed traffic is already E2E ciphertext.
'turn:144.172.96.126:3478?transport=udp',
'turn:144.172.96.126:3478?transport=tcp',
'turns:turn.securebit.chat:443?transport=tcp'
],
username: '2147483647:securebit',
credential: 'tNrw3h2p/+OE0uLMLVc+ech7T6o='
}
{ urls: 'stun:144.172.96.126:3478' }
// SecureBit's own TURN relay (turn.securebit.chat) is deliberately not listed
// here. It no longer has a built-in password: the app asks the site for one
// that expires after a day (POST /api/turn-credentials) and adds the relay to
// this list itself — see src/network/turnCredentials.js.
];
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/de/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/de.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/de.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
Vendored
+4 -4
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -7,7 +7,7 @@ this document describes.
| | |
| --- | --- |
| Release | v6.8.4 |
| Release | v6.8.5 |
| Protocol version | 4.1 |
| Ratchet wire version | 1 |
@@ -347,5 +347,5 @@ no leg may stop a capture it borrowed.
## Scope
This describes the browser implementation as it stands in v6.8.4. It is not a
This describes the browser implementation as it stands in v6.8.5. It is not a
substitute for independent cryptographic review.
+2 -2
View File
@@ -153,7 +153,7 @@
</thead>
<tbody><tr>
<td>Release</td>
<td>v6.8.4</td>
<td>v6.8.5</td>
</tr>
<tr>
<td>Protocol version</td>
@@ -486,7 +486,7 @@
shared across every leg, and stopped when the call, the group or the tab ends;
no leg may stop a capture it borrowed.</p>
<h2 id="scope">Scope</h2>
<p>This describes the browser implementation as it stands in v6.8.4. It is not a
<p>This describes the browser implementation as it stands in v6.8.5. It is not a
substitute for independent cryptographic review.</p>
<nav class="more">
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/es/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/es.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/es.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/fa/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/fa.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/fa.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/fr/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/fr.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/fr.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/he/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/he.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/he.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/hi/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/hi.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/hi.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+18 -18
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -486,12 +486,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/ko/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/ko.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/ko.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+7 -7
View File
@@ -1,10 +1,10 @@
{
"version": "1790206279080",
"buildVersion": "1790206279080",
"appVersion": "6.8.4",
"buildTime": "2026-09-23T23:31:19.119Z",
"buildId": "1790206279080-99da907",
"gitHash": "99da907",
"version": "1790208243441",
"buildVersion": "1790208243441",
"appVersion": "6.8.5",
"buildTime": "2026-09-24T00:04:03.480Z",
"buildId": "1790208243441-f39e902",
"gitHash": "f39e902",
"generated": true,
"generatedAt": "2026-09-23T23:31:19.120Z"
"generatedAt": "2026-09-24T00:04:03.481Z"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "securebit-chat",
"version": "6.8.4",
"version": "6.8.5",
"description": "Secure P2P Communication Application with End-to-End Encryption",
"main": "index.html",
"scripts": {
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/ru/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/ru.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/ru.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+23 -23
View File
@@ -17,7 +17,7 @@
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -37,7 +37,7 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -57,7 +57,7 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -77,7 +77,7 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -97,7 +97,7 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -117,7 +117,7 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -137,7 +137,7 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -157,7 +157,7 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -177,7 +177,7 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -197,7 +197,7 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -217,7 +217,7 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -237,7 +237,7 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
@@ -257,67 +257,67 @@
<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="x-default" href="https://securebit.chat/"/>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://securebit.chat/docs/</loc>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://securebit.chat/docs/faq/</loc>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://securebit.chat/docs/architecture/</loc>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://securebit.chat/docs/cryptography/</loc>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://securebit.chat/docs/descriptor-sbq2/</loc>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://securebit.chat/docs/configuration/</loc>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://securebit.chat/docs/calls/</loc>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://securebit.chat/docs/api/</loc>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://securebit.chat/docs/contributing/</loc>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://securebit.chat/docs/use-policy/</loc>
<lastmod>2026-09-23</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
+18 -4
View File
@@ -10,9 +10,12 @@
// connection — and the next in-band ICE restart, which re-reads the list — picks
// up the new credential without anything else having to know it changed.
//
// If the endpoint cannot be reached, nothing is changed and the entry keeps the
// credential it shipped with. Only our relay's entries are touched; a TURN server
// the user configured themselves is never modified.
// The web build ships no relay entry at all: a TURN entry without a credential
// is refused by RTCPeerConnection, and a built-in credential is exactly what this
// replaced. So the first successful fetch ADDS the entry, and later ones renew it.
// Until then — or if the site cannot be reached — connections use STUN only.
// Only our relay's entries are touched; a TURN server the user configured
// themselves is never modified.
const ENDPOINT = '/api/turn-credentials';
const OWN_RELAY_HOSTS = ['turn.securebit.chat', '144.172.96.126'];
@@ -42,7 +45,8 @@ function isCleanField(value) {
}
/**
* Write a fresh credential into every own-relay entry of `list`, in place.
* Write a fresh credential into every own-relay entry of `list`, in place. When
* the list has no such entry yet and `cred.urls` names our relay, one is added.
* @returns {boolean} whether anything was updated
*/
export function applyTurnCredentials(list, cred) {
@@ -56,6 +60,15 @@ export function applyTurnCredentials(list, cred) {
entry.credential = cred.credential;
updated = true;
}
if (!updated && Array.isArray(cred.urls)) {
// Only our relay's addresses may come from the response: the endpoint
// is trusted to renew a password, not to point connections elsewhere.
const urls = cred.urls.filter((u) => isCleanField(u) && OWN_RELAY_HOSTS.includes(relayHost(u)));
if (urls.length > 0) {
list.push({ urls, username: cred.username, credential: cred.credential });
updated = true;
}
}
return updated;
}
@@ -68,6 +81,7 @@ export function parseCredentialResponse(body, nowSeconds) {
if (!Number.isFinite(expiry) || expiry <= nowSeconds) return null;
const ttl = Number(body.ttl);
return {
urls: Array.isArray(server.urls) ? server.urls.slice(0, 8) : [],
username: server.username,
credential: server.credential,
expiry,
+1 -1
View File
@@ -11,7 +11,7 @@ let DYNAMIC_CACHE = 'securebit-pwa-dynamic-v4.7.56';
// Build stamp — rewritten by scripts/post-build.js on every release so this file's
// 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.
const SW_BUILD_VERSION = '1790206279080';
const SW_BUILD_VERSION = '1790208243441';
// 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
+40 -1
View File
@@ -3,7 +3,7 @@
import assert from 'node:assert/strict';
import crypto from 'node:crypto';
import endpoint from '../deploy/turn-credentials.js';
import { applyTurnCredentials, isOwnRelayEntry } from '../src/network/turnCredentials.js';
import { applyTurnCredentials, isOwnRelayEntry, parseCredentialResponse } from '../src/network/turnCredentials.js';
const SECRET = 'test-secret';
const NOW = 1_800_000_000;
@@ -89,4 +89,43 @@ const ownEntry = () => ({
assert.equal(own.credential, 'old');
}
{
// The web build ships no relay entry: the first fetch adds it.
const stun = { urls: 'stun:stun.l.google.com:19302' };
const list = [stun];
const body = endpoint.decide({ method: 'POST', origin: 'https://securebit.chat', secret: SECRET, nowSeconds: NOW }).body;
const cred = parseCredentialResponse(body, NOW);
assert.ok(cred, 'a real endpoint response parses');
assert.equal(applyTurnCredentials(list, cred), true);
assert.equal(list.length, 2, 'the relay is added to the same array');
assert.equal(list[0], stun);
assert.deepEqual(list[1].urls, endpoint.TURN_URLS);
assert.equal(list[1].credential, cred.credential);
// A second fetch renews that entry instead of adding another.
const next = parseCredentialResponse(endpoint.decide({ method: 'POST', origin: undefined, secret: SECRET, nowSeconds: NOW + 60 }).body, NOW + 60);
applyTurnCredentials(list, next);
assert.equal(list.length, 2);
assert.equal(list[1].username, next.username);
// The response may renew a password; it may not point us at another server.
const hijack = [stun];
assert.equal(applyTurnCredentials(hijack, { username: 'u:securebit', credential: 'c', urls: ['turn:relay.evil.example:3478'] }), false);
assert.equal(hijack.length, 1);
const mixed = [];
applyTurnCredentials(mixed, { username: 'u:securebit', credential: 'c', urls: ['turn:relay.evil.example:3478', 'turn:turn.securebit.chat:3478'] });
assert.deepEqual(mixed[0].urls, ['turn:turn.securebit.chat:3478']);
// Expired credentials are refused outright.
assert.equal(parseCredentialResponse(body, NOW + endpoint.TTL_SECONDS + 1), null);
}
// The shipped production config carries no relay password any more.
{
const fs = await import('node:fs');
const prod = fs.readFileSync(new URL('../config/ice-servers.prod.js', import.meta.url), 'utf8');
assert.doesNotMatch(prod, /credential\s*:/, 'config/ice-servers.prod.js must not ship a TURN password');
assert.doesNotMatch(prod, /urls:\s*\[?\s*'turns?:/, 'the relay is added at runtime, not listed statically');
}
console.log('turn-credentials.test.mjs: all assertions passed');
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/uk/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/uk.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/uk.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/ur/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/ur.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/ur.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>
+19 -19
View File
@@ -30,18 +30,18 @@
<!-- PWA Manifest -->
<link rel="manifest" href="/zh/manifest.json">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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). -->
<script src="/src/scripts/theme-boot.js?v=1790206279080"></script>
<script src="/src/scripts/theme-boot.js?v=1790208243441"></script>
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
not replayed, so the listener has to exist before Chrome decides the page
is installable. Deferred/module scripts are already too late. -->
<script src="/src/scripts/pwa-install-capture.js?v=1790206279080"></script>
<script src="/src/scripts/pwa-install-capture.js?v=1790208243441"></script>
<!-- PWA Meta Tags -->
<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">
<!-- Apple Touch Icons -->
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790206279080">
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790208243441">
<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="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="144x144" href="/logo/icon-144x144.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=1790206279080">
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790208243441">
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#ff6b35">
@@ -264,7 +264,7 @@
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
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. -->
<script defer src="/config/ice-servers.js?v=1790206279080"></script>
<script defer src="/config/ice-servers.js?v=1790208243441"></script>
<script defer src="/libs/react/react.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 —
@@ -278,8 +278,8 @@
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
connection. Bytes were never the problem; requests were. -->
<link rel="stylesheet" href="/assets/app.css?v=1790206279080">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790206279080">
<link rel="stylesheet" href="/assets/app.css?v=1790208243441">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790208243441">
<!-- 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
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/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
<script defer src="/src/scripts/load-async-css.js?v=1790206279080"></script>
<script defer src="/src/scripts/load-async-css.js?v=1790208243441"></script>
<noscript>
<link rel="stylesheet" href="/libs/prism/prism.css">
</noscript>
<script defer src="/src/scripts/fa-check.js?v=1790206279080"></script>
<script defer src="/src/scripts/fa-check.js?v=1790208243441"></script>
<!-- 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()
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. -->
<script type="module" src="/src/i18n/dict/zh.js?v=1790206279080"></script>
<script type="module" src="/src/i18n/dict/zh.js?v=1790208243441"></script>
<!-- Update Manager - система принудительного обновления -->
<script defer src="/src/utils/updateManager.js?v=1790206279080"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790206279080"></script>
<script defer src="/src/utils/updateManager.js?v=1790208243441"></script>
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790208243441"></script>
<!-- /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
first load. src/components/QRScanner.js used to be here too and registered
@@ -487,12 +487,12 @@
</div>
</div>
</div>
<script type="module" src="/dist/app-boot.js?v=1790206279080"></script>
<script type="module" src="/dist/app.js?v=1790206279080"></script>
<script type="module" src="/dist/app-boot.js?v=1790208243441"></script>
<script type="module" src="/dist/app.js?v=1790208243441"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790206279080"></script>
<script src="/src/pwa/install-prompt.js?v=1790206279080" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790206279080" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790206279080"></script>
<script defer src="/src/scripts/pwa-register.js?v=1790208243441"></script>
<script src="/src/pwa/install-prompt.js?v=1790208243441" type="module"></script>
<script src="/src/pwa/pwa-manager.js?v=1790208243441" type="module"></script>
<script defer src="/src/scripts/pwa-offline-test.js?v=1790208243441"></script>
</body>
</html>