Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
026c81b775 | ||
|
|
f71ff62417 | ||
|
|
ce48e8a851 | ||
|
|
cc7f850e7d | ||
|
|
0a42aa13c3 | ||
|
|
4b8c8829f1 | ||
|
|
ebcf2dcaac | ||
|
|
f136d0db3d | ||
|
|
91c292a6cf | ||
|
|
1b6431a36b | ||
|
|
ee635da66f | ||
|
|
92907732bf | ||
|
|
968fff9c61 | ||
|
|
79c8d08782 | ||
|
|
207e51361c | ||
|
|
4583db39a2 | ||
|
|
77ed4b3e4f | ||
|
|
c7b16157fc | ||
|
|
3c2bac588c | ||
|
|
2ae65eb749 | ||
|
|
4a3249b2a6 | ||
|
|
456545f055 | ||
|
|
40381cc0a1 | ||
|
|
0d7835cfa2 | ||
|
|
7604381584 | ||
|
|
a7a5861e0a | ||
|
|
63a19e6a4c | ||
|
|
b18e943abd | ||
|
|
9c1258cd1e | ||
|
|
60e4bb6b8a | ||
|
|
4233ba3d7e | ||
|
|
333c4b87b5 | ||
|
|
5096a09811 | ||
|
|
5c658cdd6c | ||
|
|
e209d3e6f5 | ||
|
|
49e5a054cb | ||
|
|
9d0372484a | ||
|
|
69fa5ffc28 | ||
|
|
ed169b4c8c | ||
|
|
906562333e | ||
|
|
4e7f5867b5 | ||
|
|
5ddfd1f5b3 | ||
|
|
1acbc12a92 | ||
|
|
3ed8766fc9 | ||
|
|
b087adfecc | ||
|
|
5b5cc67fdc | ||
|
|
93e4b7ad19 | ||
|
|
fce18d27a5 | ||
|
|
03e06f59dc | ||
|
|
5df6cb0d16 | ||
|
|
3730f46a08 | ||
|
|
abd6f9a7f5 | ||
|
|
99469e5bd5 | ||
|
|
2306b32482 | ||
|
|
cbb6a8fa31 | ||
|
|
4be6fc55f5 | ||
|
|
cb62d8eb3b | ||
|
|
ea8cf47118 | ||
|
|
d292c84829 | ||
|
|
45de8ce285 | ||
|
|
7af8f528ff | ||
|
|
d24d578321 | ||
|
|
dd9f13ab59 | ||
|
|
2e5b078bc2 | ||
|
|
75fa1cd27d | ||
|
|
60db79d2ae | ||
|
|
204e1edd9a | ||
|
|
5b557102b4 | ||
|
|
e6d6576d70 | ||
|
|
d2830b9c46 | ||
|
|
ec04bebf22 | ||
|
|
8cd78a2aba | ||
|
|
4359e5fab1 | ||
|
|
78769b58ef | ||
|
|
afc873761f | ||
|
|
7682f57889 | ||
|
|
6bed472e09 | ||
|
|
65cc136b99 | ||
|
|
5ce6db1640 | ||
|
|
0dde4906d9 | ||
|
|
0a7a830b52 | ||
|
|
49347b5c78 | ||
|
|
85d900727d | ||
|
|
7902359c48 | ||
|
|
0ce05b836b | ||
|
|
34094956b7 | ||
|
|
804b384271 | ||
|
|
5034386ee6 | ||
|
|
14b6ae2337 | ||
|
|
0f8399ec88 | ||
|
|
3458270477 | ||
|
|
e2316f6557 | ||
|
|
0d029f5d39 | ||
|
|
beb5bcfd01 | ||
|
|
77c19c4d71 | ||
|
|
a04fc16d58 | ||
|
|
14b001710a | ||
|
|
35d83d46f2 | ||
|
|
6c4d0eeaee | ||
|
|
39ae9f01b7 | ||
|
|
551feb21a4 | ||
|
|
a265209ff6 | ||
|
|
97b87828e2 | ||
|
|
a4161bc47e | ||
|
|
e468838596 | ||
|
|
398d8bc014 | ||
|
|
6aaabbd1df | ||
|
|
0b01083fce | ||
|
|
984564fae6 | ||
|
|
1e270fb4b8 | ||
|
|
e7c6dfc3b3 | ||
|
|
dde7196bb8 | ||
|
|
26ba6eebb9 | ||
|
|
171a7d9dfb | ||
|
|
f7940d25e9 | ||
|
|
f368defcf1 | ||
|
|
7fdf7a1b89 | ||
|
|
a28aacbe6e | ||
|
|
959e137171 | ||
|
|
434301fe6f | ||
|
|
235e3e06cb | ||
|
|
81b0e4bd3e | ||
|
|
cef5654cf8 | ||
|
|
3eab0588db | ||
|
|
430ff2a6af | ||
|
|
31485989f7 | ||
|
|
9b2884a3af | ||
|
|
7ee5ec6208 | ||
|
|
2fb7e47e05 | ||
|
|
73850f423c | ||
|
|
34289d7b9c |
@@ -0,0 +1,32 @@
|
||||
name: "CodeQL Analysis"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
- cron: '0 3 * * *'
|
||||
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze CodeQL
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: javascript,typescript
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
@@ -0,0 +1,47 @@
|
||||
name: Deploy Application
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build application
|
||||
run: npm run build
|
||||
|
||||
- name: Deploy to server
|
||||
# Добавьте ваш процесс деплоя здесь
|
||||
# Например: rsync, scp, FTP, etc.
|
||||
run: |
|
||||
echo "Deploying to server..."
|
||||
# Ваш скрипт деплоя
|
||||
# Пример для rsync:
|
||||
# rsync -avz --delete dist/ user@server:/var/www/securebit-chat/public/
|
||||
|
||||
# Автоматическая очистка кеша Cloudflare отключена
|
||||
# Для ручной очистки используйте: node scripts/purge-cloudflare-cache.js
|
||||
# Или очистите кеш через Cloudflare Dashboard
|
||||
|
||||
- name: Verify deployment
|
||||
run: |
|
||||
echo "✅ Deployment completed"
|
||||
echo "Note: Cloudflare cache purge is disabled. Clear cache manually if needed."
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Mirror to Codeberg
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.CODEBERG_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan codeberg.org >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Push to Codeberg
|
||||
run: |
|
||||
git remote remove codeberg || true
|
||||
git remote add codeberg git@codeberg.org:SecureBitChat/securebit-chat.git
|
||||
git push codeberg main --force
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Mirror to PrivacyGuides
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Push to PrivacyGuides (HTTPS)
|
||||
env:
|
||||
PRIVACYGUIDES_TOKEN: ${{ secrets.PRIVACYGUIDES_TOKEN }}
|
||||
run: |
|
||||
git remote remove privacyguides || true
|
||||
git remote add privacyguides https://SecureBitChat:${PRIVACYGUIDES_TOKEN}@code.privacyguides.dev/SecureBitChat/securebit-chat.git
|
||||
git push privacyguides main --force
|
||||
#New
|
||||
@@ -0,0 +1,6 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Local environment noise
|
||||
.npm/
|
||||
npm-debug.log*
|
||||
@@ -0,0 +1,199 @@
|
||||
# SecureBit.chat - Apache Configuration
|
||||
# Comprehensive caching configuration for forced updates
|
||||
|
||||
# ============================================
|
||||
# MIME TYPES - MUST BE FIRST (before other rules)
|
||||
# ============================================
|
||||
# Critical: Set MIME types BEFORE any other rules to ensure correct Content-Type headers
|
||||
<IfModule mod_mime.c>
|
||||
# JavaScript modules - explicit order matters
|
||||
AddType application/javascript .jsx
|
||||
AddType application/javascript .mjs
|
||||
AddType application/javascript .js
|
||||
AddType application/json .json
|
||||
|
||||
# Fonts
|
||||
AddType font/woff .woff
|
||||
AddType font/woff2 .woff2
|
||||
AddType application/font-woff .woff
|
||||
AddType application/font-woff2 .woff2
|
||||
|
||||
# Service Worker
|
||||
AddType application/manifest+json .webmanifest
|
||||
</IfModule>
|
||||
|
||||
# Force Content-Type headers (override any server defaults)
|
||||
<IfModule mod_headers.c>
|
||||
# All JavaScript files including JSX - CRITICAL for ES modules
|
||||
<FilesMatch "\.(js|mjs|jsx)$">
|
||||
Header always set Content-Type "application/javascript; charset=utf-8"
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
# Enable mod_rewrite
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
</IfModule>
|
||||
|
||||
# ============================================
|
||||
# CRITICAL FILES - NO CACHING
|
||||
# ============================================
|
||||
|
||||
# meta.json - versioning file (never cache)
|
||||
<FilesMatch "meta\.json$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
|
||||
Header set Pragma "no-cache"
|
||||
Header set Expires "0"
|
||||
Header set X-Content-Type-Options "nosniff"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# HTML files - always fresh
|
||||
<FilesMatch "\.(html|htm)$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
|
||||
Header set Pragma "no-cache"
|
||||
Header set Expires "0"
|
||||
# Remove ETag for validation
|
||||
Header unset ETag
|
||||
FileETag None
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# Service Worker - no cache
|
||||
<FilesMatch "sw\.js$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
|
||||
Header set Pragma "no-cache"
|
||||
Header set Expires "0"
|
||||
Header set Service-Worker-Allowed "/"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# manifest.json - no cache
|
||||
<FilesMatch "manifest\.json$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
|
||||
Header set Pragma "no-cache"
|
||||
Header set Expires "0"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# ============================================
|
||||
# STATIC RESOURCES - AGGRESSIVE CACHING
|
||||
# ============================================
|
||||
|
||||
# JavaScript files in dist/ - no cache (for updates)
|
||||
<FilesMatch "^dist/.*\.(js|mjs)$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
|
||||
Header set Pragma "no-cache"
|
||||
Header set Expires "0"
|
||||
Header set X-Content-Type-Options "nosniff"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# JavaScript files with hashes in other locations - long cache
|
||||
<FilesMatch "\.(js|mjs)$">
|
||||
<IfModule mod_headers.c>
|
||||
# Files with hashes in name - cache for one year
|
||||
Header set Cache-Control "public, max-age=31536000, immutable"
|
||||
Header set X-Content-Type-Options "nosniff"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# CSS files - long cache
|
||||
<FilesMatch "\.css$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "public, max-age=31536000, immutable"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# Images - long cache
|
||||
<FilesMatch "\.(jpg|jpeg|png|gif|webp|svg|ico)$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "public, max-age=31536000, immutable"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# Fonts - long cache
|
||||
<FilesMatch "\.(woff|woff2|ttf|otf|eot)$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "public, max-age=31536000, immutable"
|
||||
Header set Access-Control-Allow-Origin "*"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# Audio/Video - long cache
|
||||
<FilesMatch "\.(mp3|mp4|webm|ogg)$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Cache-Control "public, max-age=31536000, immutable"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# ============================================
|
||||
# SECURITY
|
||||
# ============================================
|
||||
|
||||
# XSS Protection
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-XSS-Protection "1; mode=block"
|
||||
Header set X-Content-Type-Options "nosniff"
|
||||
Header set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
Header set X-Frame-Options "DENY"
|
||||
</IfModule>
|
||||
|
||||
# Content Security Policy (frame-ancestors and report-uri only work in HTTP headers, not meta tags)
|
||||
<IfModule mod_headers.c>
|
||||
Header set Content-Security-Policy "frame-ancestors 'none'; report-uri /csp-report; report-to csp-endpoint;"
|
||||
</IfModule>
|
||||
|
||||
# ============================================
|
||||
# GZIP COMPRESSION
|
||||
# ============================================
|
||||
|
||||
<IfModule mod_deflate.c>
|
||||
# Compress text files
|
||||
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json application/xml
|
||||
|
||||
# Compress fonts
|
||||
AddOutputFilterByType DEFLATE font/woff font/woff2 application/font-woff application/font-woff2
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ============================================
|
||||
# CLOUDFLARE RULES
|
||||
# ============================================
|
||||
|
||||
# Cloudflare can cache static files, but should not cache:
|
||||
# - meta.json
|
||||
# - index.html
|
||||
# - sw.js
|
||||
# - manifest.json
|
||||
|
||||
# These rules are applied at Cloudflare Page Rules level
|
||||
# (see CLOUDFLARE_SETUP.md documentation)
|
||||
|
||||
# ============================================
|
||||
# SPA FALLBACK
|
||||
# ============================================
|
||||
|
||||
# If file not found, redirect to index.html (for SPA routing)
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} !^/meta\.json$
|
||||
RewriteCond %{REQUEST_URI} !^/sw\.js$
|
||||
RewriteCond %{REQUEST_URI} !^/manifest\.json$
|
||||
RewriteRule ^(.*)$ /index.html [L]
|
||||
</IfModule>
|
||||
|
||||
# ============================================
|
||||
# LOGGING (optional)
|
||||
# ============================================
|
||||
|
||||
# Uncomment for debugging
|
||||
# LogLevel rewrite:trace3
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## v4.8.5 — Security hardening release
|
||||
|
||||
This release consolidates several months of security, privacy, and lifecycle hardening work by the SecureBit.chat team.
|
||||
|
||||
### Security
|
||||
|
||||
- Added mandatory interactive SAS verification; passive click-through confirmation is no longer sufficient.
|
||||
- Made SAS computation deterministic across peers using shared session material.
|
||||
- Enforced protocol version `4.1` mismatch handling for incompatible clients.
|
||||
- Added TURN relay-only privacy mode and explicit warnings when TURN is unavailable.
|
||||
- Encrypted sensitive IndexedDB metadata and added safe lazy migration for legacy plaintext records.
|
||||
- Added mandatory consent gating for every incoming file transfer.
|
||||
- Replaced broad file acceptance with an explicit file-type allowlist and spoofing checks.
|
||||
- Sanitized every incoming decrypted chat message before UI delivery.
|
||||
|
||||
### Reliability and resource lifecycle
|
||||
|
||||
- Consolidated disconnect behavior into one canonical cleanup path.
|
||||
- Added cleanup for tracked timers, deferred retries, peer-disconnect scheduling, and fake/decoy traffic.
|
||||
- Rejected pending sender consent promises immediately during cleanup.
|
||||
- Bounded retained received-file buffers and added graceful handling for expired download handles.
|
||||
- Cleared React file-transfer UI state and detached live callbacks on unmount.
|
||||
- Improved reconnect hygiene and stale-session cleanup behavior.
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Pinned dependency versions.
|
||||
- Applied safe transitive patch/minor updates.
|
||||
- Verified a clean `npm audit` result.
|
||||
- Expanded regression coverage for SAS, file consent, sanitization, privacy mode, metadata encryption, cleanup, and callback lifecycle behavior.
|
||||
@@ -1,417 +1,121 @@
|
||||
# SecureBit.chat - Enhanced Security Edition
|
||||
# SecureBit.chat v4.8.5
|
||||
|
||||
<div align="center">
|
||||
SecureBit.chat is a browser-based peer-to-peer chat application built on WebRTC and Web Crypto APIs. It is designed for direct encrypted communication, explicit peer verification, and a small operational footprint without account registration or server-side message storage.
|
||||
|
||||

|
||||
## Security model
|
||||
|
||||
**The world's first P2P messenger with Lightning Network payments and military-grade cryptography**
|
||||
SecureBit.chat uses:
|
||||
|
||||
[](https://github.com/SecureBitChat/securebit-chat/releases/latest)
|
||||
[](https://securebitchat.github.io/securebit-chat/)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[]()
|
||||
- ECDH key agreement with derived session keys
|
||||
- DTLS-protected WebRTC transport
|
||||
- deterministic Short Authentication String (SAS) verification
|
||||
- end-to-end encrypted chat payloads
|
||||
- replay protection and session-state cleanup
|
||||
- encrypted local key metadata in IndexedDB
|
||||
|
||||
</div>
|
||||
A session is not treated as verified until both peers complete the interactive SAS flow. Each user must compare the displayed code with the peer through an out-of-band channel and enter the matching code manually. Three failed SAS attempts terminate the session.
|
||||
|
||||
---
|
||||
## Highlights in v4.8.5
|
||||
|
||||
## 🚀 Try It Now
|
||||
This release consolidates several months of security hardening work by the project team:
|
||||
|
||||
### 🌐 [Live Demo — SecureBit.chat](https://securebitchat.github.io/securebit-chat/)
|
||||
- mandatory interactive SAS verification instead of passive click-through confirmation
|
||||
- deterministic SAS computation from shared session material
|
||||
- protocol version `4.1` negotiation with mismatch rejection
|
||||
- optional TURN relay-only privacy mode with clear warnings when TURN is unavailable
|
||||
- encrypted IndexedDB metadata with lazy migration from legacy plaintext records
|
||||
- explicit file-transfer consent before any receive buffers are allocated
|
||||
- strict file-type allowlist using both MIME type and extension checks
|
||||
- incoming decrypted message sanitization before UI delivery
|
||||
- improved disconnect, timer, file-transfer, and React UI cleanup behavior
|
||||
- pinned dependency versions and a clean `npm audit` baseline
|
||||
|
||||
*No installation required — works directly in your browser with military-grade encryption.*
|
||||
## Quick start
|
||||
|
||||
---
|
||||
|
||||
## ✨ What Makes SecureBit.chat Unique
|
||||
|
||||
### 🏆 Industry Leader
|
||||
|
||||
* **Dominates in 11/15 security categories** vs Signal, Threema, Session
|
||||
* **First messenger** with Lightning Network integration
|
||||
* **Military-grade cryptography** exceeding government standards
|
||||
* **Zero servers** — truly decentralized P2P architecture
|
||||
|
||||
### ⚡ Lightning Network Pioneer
|
||||
|
||||
* **Instant satoshi payments** for secure sessions
|
||||
* **Pay-per-session model** — no ads, no data harvesting
|
||||
* **WebLN integration** with all major Lightning wallets
|
||||
* **Sustainable economics** for private communication
|
||||
|
||||
### 🔐 12-Layer Military Security
|
||||
|
||||
1. **WebRTC DTLS** — Transport encryption
|
||||
2. **ECDH P-384** — Perfect forward secrecy
|
||||
3. **AES-GCM 256** — Authenticated encryption
|
||||
4. **ECDSA P-384** — Message integrity
|
||||
5. **Replay protection** — Timestamp validation
|
||||
6. **Key rotation** — Every 5 minutes/100 messages
|
||||
7. **MITM verification** — Out-of-band codes
|
||||
8. **Traffic obfuscation** — Pattern masking
|
||||
9. **Metadata protection** — Zero leakage
|
||||
10. **Memory protection** — No persistent storage
|
||||
11. **Hardware security** — Non-extractable keys
|
||||
12. **Session isolation** — Complete cleanup
|
||||
|
||||
### 🎭 Advanced Privacy
|
||||
|
||||
* **Complete anonymity** — no registration required
|
||||
* **Zero data collection** — messages only in browser memory
|
||||
* **Traffic analysis resistance** — fake traffic generation
|
||||
* **Censorship resistance** — no servers to block
|
||||
* **Instant anonymous channels** — connect in seconds
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Security Comparison
|
||||
|
||||
| Feature | **SecureBit.chat** | Signal | Threema | Session |
|
||||
| --------------------------- | ----------------------------- | ---------------------------- | --------------------- | ---------------------- |
|
||||
| **Architecture** | 🏆 Pure P2P WebRTC | ❌ Centralized servers | ❌ Centralized servers | ⚠️ Onion network |
|
||||
| **Payment Integration** | 🏆 Lightning Network | ❌ None | ❌ None | ❌ None |
|
||||
| **Registration** | 🏆 Anonymous | ❌ Phone required | ✅ ID generated | ✅ Random ID |
|
||||
| **Traffic Obfuscation** | 🏆 Advanced fake traffic | ❌ None | ❌ None | ✅ Onion routing |
|
||||
| **Censorship Resistance** | 🏆 Hard to block | ⚠️ Blocked in some countries | ⚠️ May be blocked | ✅ Onion routing |
|
||||
| **Data Storage** | 🏆 Zero storage | ⚠️ Local database | ⚠️ Local + backup | ⚠️ Local database |
|
||||
| **Economic Model** | 🏆 Pay‑per‑session | ⚠️ Donations dependent | ✅ One‑time purchase | ⚠️ Donations dependent |
|
||||
| **Metadata Protection** | 🏆 Full encryption | ⚠️ Sealed Sender (partial) | ⚠️ Minimal metadata | ✅ Onion routing |
|
||||
| **Key Security** | 🏆 Non‑extractable + hardware | ✅ Secure storage | ✅ Local storage | ✅ Secure storage |
|
||||
| **Perfect Forward Secrecy** | 🏆 Auto rotation (5 min) | ✅ Double Ratchet | ⚠️ Partial (groups) | ✅ Session Ratchet |
|
||||
| **Open Source** | 🏆 100% + auditable | ✅ Fully open | ⚠️ Only clients | ✅ Fully open |
|
||||
|
||||
**Legend:** 🏆 Category Leader | ✅ Excellent | ⚠️ Partial/Limited | ❌ Not Available
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Option 1: Use Online (Recommended)
|
||||
|
||||
1. **Visit:** [https://securebitchat.github.io/securebit-chat/](https://securebitchat.github.io/securebit-chat/)
|
||||
2. **Choose:** *Create Channel* or *Join Channel*
|
||||
3. **Complete:** Secure key exchange with verification
|
||||
4. **Select:** Session type (Demo / Basic / Premium)
|
||||
5. **Communicate:** With military‑grade encryption
|
||||
|
||||
### Option 2: Self‑Host
|
||||
### Run locally
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/SecureBitChat/securebit-chat.git
|
||||
cd securebit-chat
|
||||
|
||||
# Serve locally (choose one method)
|
||||
python -m http.server 8000 # Python
|
||||
npx serve . # Node.js
|
||||
php -S localhost:8000 # PHP
|
||||
|
||||
# Open browser
|
||||
open http://localhost:8000
|
||||
npm install
|
||||
npm run build
|
||||
npm run serve
|
||||
```
|
||||
|
||||
---
|
||||
Then open the local server URL in two browser windows or profiles.
|
||||
|
||||
## ⚡ Lightning Network Integration
|
||||
### Establish a session
|
||||
|
||||
### Session Types
|
||||
1. Create an offer in the first browser.
|
||||
2. Transfer the offer to the peer and create an answer.
|
||||
3. Return the answer to the first browser.
|
||||
4. Compare the SAS code out of band.
|
||||
5. Enter the matching SAS code on both sides.
|
||||
6. Begin chatting only after both peers are verified.
|
||||
|
||||
* **🎮 Demo:** 6 minutes free (testing)
|
||||
* **⚡ Basic:** 1 hour for 50 satoshis
|
||||
* **💎 Premium:** 6 hours for 200 satoshis
|
||||
## Configuration
|
||||
|
||||
### Supported Wallets
|
||||
### TURN / privacy mode
|
||||
|
||||
| Wallet | WebLN | Mobile | Desktop |
|
||||
| ----------------- | :---: | :----: | :-----: |
|
||||
| Alby | ✅ | ✅ | ✅ |
|
||||
| Zeus | ✅ | ✅ | ✅ |
|
||||
| Wallet of Satoshi | ✅ | ✅ | ❌ |
|
||||
| Muun | ⚠️ | ✅ | ❌ |
|
||||
| Breez | ✅ | ✅ | ❌ |
|
||||
| Strike | ✅ | ✅ | ✅ |
|
||||
Direct WebRTC connections may expose IP addresses to peers. SecureBit.chat supports a relay-only privacy mode:
|
||||
|
||||
*And many more WebLN‑compatible wallets.*
|
||||
- default mode keeps normal WebRTC behavior and existing STUN support
|
||||
- relay-only mode sets `iceTransportPolicy: "relay"`
|
||||
- relay-only mode requires a configured TURN server
|
||||
- STUN alone does not hide IP addresses
|
||||
- public TURN credentials are not bundled or hardcoded
|
||||
|
||||
---
|
||||
Configure ICE servers at deployment time and enable relay-only mode only when a TURN service is available. See [`doc/CONFIGURATION.md`](doc/CONFIGURATION.md).
|
||||
|
||||
## 🔧 Technical Architecture
|
||||
### File transfer policy
|
||||
|
||||
### Cryptographic Stack
|
||||
Incoming file transfers require explicit user consent. Before the consent prompt appears, metadata is validated and dangerous names are rejected. Safe accepted categories are:
|
||||
|
||||
```
|
||||
🔐 Application Layer: AES-GCM 256-bit + ECDSA P-384
|
||||
🔑 Key Exchange: ECDH P-384 (Perfect Forward Secrecy)
|
||||
🛡️ Transport Layer: WebRTC DTLS 1.2
|
||||
🌐 Network Layer: P2P WebRTC Data Channels
|
||||
⚡ Payment Layer: Lightning Network + WebLN
|
||||
```
|
||||
- common raster images
|
||||
- PDF
|
||||
- plain text
|
||||
- ZIP archives
|
||||
|
||||
### Security Standards
|
||||
Executable, scriptable, and high-risk formats are rejected, including `.exe`, `.bat`, `.cmd`, `.sh`, `.js`, `.msi`, `.dmg`, `.app`, `.jar`, `.scr`, `.ps1`, `.vbs`, `.html`, and `.svg`. MIME type and filename extension must agree.
|
||||
|
||||
* NIST SP 800‑56A — ECDH Key Agreement
|
||||
* NIST SP 800‑186 — Elliptic Curve Cryptography
|
||||
* RFC 6090 — Fundamental ECC Algorithms
|
||||
* RFC 8446 — TLS 1.3 for WebRTC
|
||||
## Development
|
||||
|
||||
### Browser Requirements
|
||||
### Requirements
|
||||
|
||||
* Modern browser with WebRTC support (Chrome 60+, Firefox 60+, Safari 12+)
|
||||
* HTTPS connection (required for WebRTC)
|
||||
* JavaScript enabled
|
||||
* Lightning wallet with WebLN (for payments)
|
||||
- Node.js 18+
|
||||
- npm
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Development Roadmap
|
||||
|
||||
**Current:** v4.0 — Enhanced Security Edition ✅
|
||||
|
||||
* 12‑layer military‑grade security
|
||||
* Lightning Network payments
|
||||
* Pure P2P WebRTC architecture
|
||||
* Advanced traffic obfuscation
|
||||
|
||||
**Next Releases**
|
||||
|
||||
### v4.5 (Q2 2025) — Mobile & Desktop Apps
|
||||
|
||||
* PWA with offline support
|
||||
* Electron desktop application
|
||||
* Push notifications
|
||||
* Cross‑device synchronization
|
||||
|
||||
### v5.0 (Q4 2025) — Quantum‑Resistant Edition
|
||||
|
||||
* CRYSTALS‑Kyber post‑quantum key exchange
|
||||
* SPHINCS+ post‑quantum signatures
|
||||
* Hybrid classical + post‑quantum schemes
|
||||
* Quantum‑safe migration path
|
||||
|
||||
### v5.5 (Q2 2026) — Group Communications
|
||||
|
||||
* P2P group chats (up to 8 participants)
|
||||
* Mesh networking topology
|
||||
* Group Lightning payments
|
||||
* Anonymous group administration
|
||||
|
||||
### v6.0 (2027) — Decentralized Network
|
||||
|
||||
* DHT‑based peer discovery
|
||||
* Built‑in onion routing
|
||||
* Decentralized identity system
|
||||
* Node incentive mechanisms
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
securebit-chat/
|
||||
├── index.html # Main application
|
||||
├── src/
|
||||
│ ├── components/ui/ # React UI components
|
||||
│ ├── crypto/ # Cryptographic utilities
|
||||
│ ├── network/ # WebRTC P2P manager
|
||||
│ ├── session/ # Payment session manager
|
||||
│ └── styles/ # CSS styling
|
||||
├── logo/ # Wallet logos and icons
|
||||
├── docs/ # Documentation
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
### Technology Stack
|
||||
|
||||
* **Frontend:** Pure JavaScript + React (via CDN)
|
||||
* **Cryptography:** Web Crypto API + custom ECDH/ECDSA
|
||||
* **Network:** WebRTC P2P Data Channels
|
||||
* **Payments:** Lightning Network via WebLN
|
||||
* **Styling:** TailwindCSS + custom CSS
|
||||
|
||||
### Development Setup
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/SecureBitChat/securebit-chat.git
|
||||
cd securebit-chat
|
||||
|
||||
# No build process required — pure client‑side
|
||||
# Just serve the files over HTTPS
|
||||
|
||||
# For development
|
||||
python -m http.server 8000
|
||||
|
||||
# For production
|
||||
# Deploy to any static hosting (GitHub Pages, Netlify, etc.)
|
||||
npm install
|
||||
npm test
|
||||
npm audit
|
||||
npm run build
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
### Project layout
|
||||
|
||||
## 🛡️ Security
|
||||
```text
|
||||
src/network/ WebRTC connection and session lifecycle
|
||||
src/transfer/ secure file-transfer implementation
|
||||
src/crypto/ cryptographic utilities
|
||||
src/components React UI components
|
||||
doc/ technical documentation
|
||||
```
|
||||
|
||||
### Security Audit Status
|
||||
## Documentation
|
||||
|
||||
* ✅ Internal cryptographic review completed
|
||||
* ✅ P2P protocol security analysis completed
|
||||
* 🔄 Professional security audit planned Q3 2025
|
||||
* 🔄 Post‑quantum cryptography review for v5.0
|
||||
- [`SECURITY.md`](SECURITY.md)
|
||||
- [`doc/CONFIGURATION.md`](doc/CONFIGURATION.md)
|
||||
- [`doc/CRYPTOGRAPHY.md`](doc/CRYPTOGRAPHY.md)
|
||||
- [`doc/SECURITY-ARCHITECTURE.md`](doc/SECURITY-ARCHITECTURE.md)
|
||||
- [`doc/API.md`](doc/API.md)
|
||||
- [`CHANGELOG.md`](CHANGELOG.md)
|
||||
|
||||
### Vulnerability Reporting
|
||||
## Responsible use
|
||||
|
||||
See **SECURITY.md** for detailed security policy and reporting instructions.
|
||||
Contact: **[SecureBitChat@proton.me](mailto:SecureBitChat@proton.me)**
|
||||
SecureBit.chat is intended for lawful, ethical use. See [`RESPONSIBLE_USE.md`](RESPONSIBLE_USE.md) and [`SECURITY_DISCLAIMER.md`](SECURITY_DISCLAIMER.md).
|
||||
|
||||
### Security Features
|
||||
## License
|
||||
|
||||
* Perfect Forward Secrecy — Past messages secure even if keys compromised
|
||||
* Out‑of‑band verification — Prevents man‑in‑the‑middle attacks
|
||||
* Traffic obfuscation — Defeats network analysis
|
||||
* Memory protection — No persistent storage of sensitive data
|
||||
* Session isolation — Complete cleanup between sessions
|
||||
|
||||
---
|
||||
|
||||
## 📊 Performance
|
||||
|
||||
### Benchmarks
|
||||
|
||||
* Connection setup: < 3 seconds
|
||||
* Message latency: < 100 ms (P2P direct)
|
||||
* Throughput: Up to 1 MB/s per connection
|
||||
* Memory usage: < 50 MB for active session
|
||||
* Battery impact: Minimal (optimized WebRTC)
|
||||
|
||||
### Scalability
|
||||
|
||||
* Concurrent connections: Limited by device capabilities
|
||||
* Message size: Up to 2000 characters
|
||||
* File transfer: Planned for v4.5
|
||||
* Group size: Up to 8 participants (v5.5)
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT License — see **LICENSE** file for details.
|
||||
|
||||
### Open Source Commitment
|
||||
|
||||
* 100% open source — full transparency
|
||||
* MIT license — maximum freedom
|
||||
* No telemetry — zero data collection
|
||||
* Community‑driven — contributions welcome
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions from the community!
|
||||
|
||||
### How to Contribute
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch: `git checkout -b feature/amazing-feature`
|
||||
3. Commit your changes: `git commit -m "Add amazing feature"`
|
||||
4. Push to the branch: `git push origin feature/amazing-feature`
|
||||
5. Open a Pull Request
|
||||
|
||||
### Contribution Areas
|
||||
|
||||
* 🔐 Cryptography — Security improvements and audits
|
||||
* 🌐 Network — P2P optimization and reliability
|
||||
* ⚡ Lightning — Payment integration enhancements
|
||||
* 🎨 UI/UX — Interface improvements and accessibility
|
||||
* 📱 Mobile — PWA and mobile optimizations
|
||||
* 📚 Documentation — Guides, tutorials, translations
|
||||
|
||||
### Development Guidelines
|
||||
|
||||
* Follow existing code style
|
||||
* Add tests for new features
|
||||
* Update documentation
|
||||
* Respect security‑first principles
|
||||
|
||||
---
|
||||
|
||||
## 📞 Contact & Support
|
||||
|
||||
### Official Channels
|
||||
|
||||
* Email: **[SecureBitChat@proton.me](mailto:SecureBitChat@proton.me)**
|
||||
* GitHub: **Issues & Discussions**
|
||||
* Security: **[SecureBitChat@proton.me](mailto:SecureBitChat@proton.me)**
|
||||
|
||||
### Community
|
||||
|
||||
* Discussions: GitHub Discussions for feature requests
|
||||
* Issues: Bug reports and technical support
|
||||
* Wiki: Documentation and guides
|
||||
|
||||
---
|
||||
|
||||
⚠️ Important Disclaimers
|
||||
Security Notice
|
||||
While SecureBit.chat implements military-grade cryptography and follows security best practices, no communication system is 100% secure. Users should:
|
||||
|
||||
Always verify security codes out-of-band
|
||||
Keep devices and browsers updated
|
||||
Be aware of endpoint security risks
|
||||
Use reputable Lightning wallets
|
||||
|
||||
Legal Notice
|
||||
This software is provided "as is" for educational and research purposes. Users are responsible for compliance with local laws and regulations regarding:
|
||||
|
||||
Cryptographic software usage
|
||||
Private communications
|
||||
Bitcoin/Lightning Network transactions
|
||||
|
||||
Privacy Statement
|
||||
SecureBit.chat:
|
||||
|
||||
Collects zero data - no analytics, tracking, or telemetry
|
||||
Stores nothing - all data exists only in browser memory
|
||||
Requires no registration - completely anonymous usage
|
||||
Uses no servers - direct P2P connections only
|
||||
|
||||
|
||||
🎯 Why Choose SecureBit.chat?
|
||||
For Privacy Advocates
|
||||
|
||||
True zero-knowledge architecture
|
||||
Military-grade encryption standards
|
||||
Complete anonymity and untraceability
|
||||
Resistance to censorship and surveillance
|
||||
|
||||
For Bitcoin/Lightning Users
|
||||
|
||||
Native Lightning Network integration
|
||||
Sustainable pay-per-session model
|
||||
Support for all major Lightning wallets
|
||||
No KYC or account requirements
|
||||
|
||||
For Developers
|
||||
|
||||
100% open source transparency
|
||||
Modern cryptographic standards
|
||||
Clean, auditable codebase
|
||||
Extensible modular architecture
|
||||
|
||||
For Everyone
|
||||
|
||||
No installation required
|
||||
Works on all modern devices
|
||||
Intuitive user interface
|
||||
Professional security standards
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
**SecureBit.chat Security Team**
|
||||
|
||||
*Committed to protecting your privacy with military-grade security*
|
||||
|
||||
**Report vulnerabilities:** SecureBitChat@proton.me
|
||||
|
||||
</div>
|
||||
MIT License. See [`LICENSE`](LICENSE).
|
||||
|
||||
@@ -1,234 +1,80 @@
|
||||
# Security Policy
|
||||
|
||||
## 🛡️ Security Overview
|
||||
## Supported release line
|
||||
|
||||
SecureBit.chat is built with security-first principles and implements **military-grade security** with 12-layer protection system. We take security vulnerabilities seriously and appreciate responsible disclosure from the security community.
|
||||
| Release | Status | Protocol |
|
||||
| --- | --- | --- |
|
||||
| v4.1.x | Supported | 4.1 |
|
||||
| earlier releases | Unsupported | legacy |
|
||||
|
||||
**Current Security Status:** 🔒 **MAXIMUM SECURITY (Stage 4)** - Exceeds government-grade communication standards
|
||||
Users should run the current supported release line to receive the latest verification, storage, and file-transfer protections.
|
||||
|
||||
## 🔒 Enhanced Security Features (Stage 4)
|
||||
## Reporting a vulnerability
|
||||
|
||||
### Multi-Layer Cryptographic Implementation
|
||||
- **Key Exchange:** ECDH P-384 (NIST recommended curve) with non-extractable keys
|
||||
- **Primary Encryption:** AES-GCM 256-bit with authenticated encryption
|
||||
- **Nested Encryption:** Additional AES-GCM 256-bit layer for maximum protection
|
||||
- **Metadata Protection:** Separate AES-GCM 256-bit encryption for message metadata
|
||||
- **Digital Signatures:** ECDSA P-384 with SHA-384 for message authenticity and MITM protection
|
||||
- **Perfect Forward Secrecy:** Automatic key rotation every 5 minutes with secure key versioning
|
||||
- **Non-extractable Keys:** All cryptographic keys are hardware-protected and non-exportable
|
||||
- **Enhanced Replay Protection:** Multi-factor protection with sequence numbers, message IDs, and timestamps
|
||||
Please report security issues privately before public disclosure.
|
||||
|
||||
### Advanced Traffic Obfuscation
|
||||
- **Packet Padding:** Random padding (64-512 bytes) to hide real message sizes
|
||||
- **Anti-Fingerprinting:** Advanced traffic pattern obfuscation and timing randomization
|
||||
- **Fake Traffic Generation:** Invisible decoy messages for traffic analysis protection
|
||||
- **Message Chunking:** Split messages into random-sized chunks with variable delays
|
||||
- **Packet Reordering Protection:** Sequence-based packet reassembly with timeout handling
|
||||
- **Decoy Channels:** Multiple fake communication channels to confuse attackers
|
||||
- Email: `SecureBitChat@proton.me`
|
||||
- Include: affected version, reproduction steps, impact, and any proof-of-concept material
|
||||
- Avoid publishing exploit details before a coordinated fix is available
|
||||
|
||||
### Enhanced Security Architecture
|
||||
- **Zero-trust Model:** No central servers to compromise
|
||||
- **P2P Direct:** WebRTC encrypted channels with enhanced validation
|
||||
- **No Data Persistence:** Messages exist only in memory, automatic cleanup
|
||||
- **Enhanced Rate Limiting:** 60 messages/minute, 5 connections/5 minutes with cryptographic verification
|
||||
- **Session Security:** 64-byte salts, unique session IDs, and replay attack prevention
|
||||
- **MITM Protection:** Out-of-band verification codes with enhanced validation
|
||||
## Current security behavior
|
||||
|
||||
## 🚨 Supported Versions
|
||||
### Peer verification
|
||||
|
||||
| Version | Security Level | Supported |
|
||||
| ------- | -------------- | ------------------ |
|
||||
| 4.0.x | MAXIMUM | ✅ Yes (12 layers)|
|
||||
| 3.x.x | HIGH | ⚠️ Limited |
|
||||
| < 3.0 | BASIC | ❌ No |
|
||||
- SAS verification is mandatory and interactive.
|
||||
- SAS values are derived deterministically from shared session material.
|
||||
- Users must compare the code out of band and enter the matching code manually.
|
||||
- A session becomes verified only after both local and remote confirmations succeed.
|
||||
- Three failed local SAS entries terminate the session.
|
||||
- Protocol version `4.1` rejects incompatible peers instead of silently falling back to older verification behavior.
|
||||
|
||||
**Recommendation:** Upgrade to 4.0.x immediately for maximum security protection.
|
||||
### Message handling
|
||||
|
||||
## 📋 Reporting a Vulnerability
|
||||
- Chat payloads remain encrypted in transit.
|
||||
- Decrypted incoming chat text is sanitized before it reaches React state or the UI.
|
||||
- Encrypted payload validation remains separate from display sanitization.
|
||||
|
||||
### 🔴 Critical Vulnerabilities
|
||||
For **critical security issues** that could compromise user safety:
|
||||
### File transfer
|
||||
|
||||
**DO NOT** create a public GitHub issue.
|
||||
- Incoming transfer metadata is validated before presentation to the user.
|
||||
- Every incoming file requires explicit Accept or Reject consent.
|
||||
- Receive buffers are not allocated before consent.
|
||||
- File names are normalized for display and dangerous names are rejected.
|
||||
- Allowed file types are explicit and validated using both MIME type and extension.
|
||||
- High-risk executable or scriptable types are blocked.
|
||||
- Repeated incoming transfer offers are rate-limited and bounded.
|
||||
|
||||
**Contact us privately:**
|
||||
- 📧 **Email:** security@SecureBit.chat (PGP key below)
|
||||
- 🔒 **Signal:** +[REDACTED] (ask for Signal number via email)
|
||||
- 🔐 **Keybase:** @SecureBitChat
|
||||
### Local storage
|
||||
|
||||
### 🟡 Non-Critical Issues
|
||||
For general security improvements or non-critical findings:
|
||||
- Create a GitHub issue with `[SECURITY]` prefix
|
||||
- Use our security issue template
|
||||
- Sensitive IndexedDB metadata is encrypted, including timestamps and session-related fields where feasible.
|
||||
- Only minimum lookup keys remain in plaintext when required.
|
||||
- Legacy plaintext metadata is migrated lazily on read.
|
||||
- Corrupted encrypted metadata fails closed.
|
||||
|
||||
## 📝 Vulnerability Disclosure Process
|
||||
### Network privacy
|
||||
|
||||
1. **Report:** Send details to security@SecureBit.chat
|
||||
2. **Acknowledgment:** We'll respond within 24 hours
|
||||
3. **Investigation:** We'll investigate and keep you updated
|
||||
4. **Fix:** We'll develop and test a fix
|
||||
5. **Disclosure:** Public disclosure after fix is deployed
|
||||
6. **Credit:** We'll credit you in our security hall of fame
|
||||
- Default mode preserves standard WebRTC connectivity.
|
||||
- Relay-only privacy mode uses TURN by setting `iceTransportPolicy: "relay"`.
|
||||
- STUN-only configurations do not provide IP protection.
|
||||
- If TURN is absent, the UI warns that direct WebRTC may expose IP addresses.
|
||||
|
||||
### Timeline Expectations
|
||||
- **Initial Response:** < 24 hours
|
||||
- **Status Update:** Every 72 hours
|
||||
- **Fix Timeline:** Critical bugs < 7 days, Others < 30 days
|
||||
### Lifecycle cleanup
|
||||
|
||||
## 🏆 Security Hall of Fame
|
||||
- Disconnect cleanup closes data channels and peer connections, clears verification state, and wipes session crypto state.
|
||||
- Timers, deferred retries, decoy traffic, pending transfers, and React file-transfer callbacks are cleaned up on shutdown.
|
||||
- Received file buffers are retained only within a bounded window and expired handles fail gracefully.
|
||||
|
||||
We maintain a hall of fame for security researchers who help improve SecureBit.chat:
|
||||
## Security verification commands
|
||||
|
||||
<!-- Security researchers will be listed here -->
|
||||
*Be the first to help secure SecureBit.chat!*
|
||||
|
||||
## 🔍 Security Audit History
|
||||
|
||||
### Independent Audits
|
||||
- **Pending:** Professional cryptographic audit (Q2 2025)
|
||||
- **Community:** Ongoing peer review by security researchers
|
||||
|
||||
### Internal Security Measures
|
||||
- **Code Review:** All cryptographic code reviewed by multiple developers
|
||||
- **Security Testing:** Comprehensive 12-layer security test suite
|
||||
- **Dependencies:** Regular security updates for all dependencies
|
||||
- **Vulnerability Testing:** Automated testing for all 12 security layers
|
||||
|
||||
## 📊 Security Architecture (Stage 4)
|
||||
|
||||
```
|
||||
12-Layer Security Architecture:
|
||||
├── Layer 1: Enhanced Authentication (ECDSA P-384 + SHA-384)
|
||||
├── Layer 2: Key Exchange (ECDH P-384, non-extractable keys)
|
||||
├── Layer 3: Metadata Protection (AES-256-GCM + 64-byte salt)
|
||||
├── Layer 4: Message Encryption (Enhanced with sequence numbers)
|
||||
├── Layer 5: Nested Encryption (Additional AES-256-GCM layer)
|
||||
├── Layer 6: Packet Padding (64-512 bytes random obfuscation)
|
||||
├── Layer 7: Anti-Fingerprinting (Advanced pattern obfuscation)
|
||||
├── Layer 8: Packet Reordering Protection (Sequence + timeout)
|
||||
├── Layer 9: Message Chunking (Random delays + sizes)
|
||||
├── Layer 10: Fake Traffic Generation (Invisible decoy messages)
|
||||
├── Layer 11: Enhanced Rate Limiting (Cryptographic verification)
|
||||
└── Layer 12: Perfect Forward Secrecy (5-minute key rotation)
|
||||
```
|
||||
|
||||
### Security Metrics
|
||||
- **Encryption Strength:** Triple-layer AES-256-GCM
|
||||
- **Key Security:** P-384 ECDH/ECDSA (equivalent to 7680-bit RSA)
|
||||
- **Forward Secrecy:** Complete (automatic key rotation)
|
||||
- **Traffic Analysis Protection:** Maximum (6-layer obfuscation)
|
||||
- **Attack Surface:** Minimal (P2P, no central servers)
|
||||
|
||||
## 🛠️ Security Best Practices for Users
|
||||
|
||||
### For Maximum Security:
|
||||
1. **Verify Authenticity:** Always verify out-of-band codes (enhanced 6-digit format)
|
||||
2. **Use Official Source:** Only use https://SecureBit.chat
|
||||
3. **Keep Updated:** Use version 4.0.x for maximum security
|
||||
4. **Secure Environment:** Use updated browsers on secure devices
|
||||
5. **Lightning Wallets:** Use reputable Lightning wallets (Alby, Zeus, etc.)
|
||||
6. **Monitor Security Status:** Check for "MAXIMUM SECURITY" indicator in chat
|
||||
|
||||
### Security Indicators:
|
||||
- ✅ **Green Shield:** MAXIMUM SECURITY (Stage 4) active
|
||||
- 🟡 **Yellow Shield:** HIGH SECURITY (Stage 3)
|
||||
- 🔴 **Red Shield:** Security issues detected
|
||||
|
||||
### Red Flags:
|
||||
- ❌ Verification codes don't match
|
||||
- ❌ Security level below Stage 4
|
||||
- ❌ Unusual connection behavior
|
||||
- ❌ Requests for private keys or seed phrases
|
||||
- ❌ Unofficial domains or mirrors
|
||||
- ❌ Missing security layer notifications
|
||||
|
||||
## 🔬 Security Research Guidelines
|
||||
|
||||
### Scope
|
||||
**In Scope:**
|
||||
- ✅ Cryptographic implementation flaws in any of the 12 layers
|
||||
- ✅ WebRTC security issues
|
||||
- ✅ Authentication bypass attempts
|
||||
- ✅ Input validation vulnerabilities
|
||||
- ✅ Client-side security issues
|
||||
- ✅ Traffic analysis vulnerabilities
|
||||
- ✅ Perfect Forward Secrecy implementation
|
||||
- ✅ Anti-fingerprinting bypass techniques
|
||||
- ✅ Fake traffic detection methods
|
||||
|
||||
**Out of Scope:**
|
||||
- ❌ Social engineering attacks
|
||||
- ❌ Physical attacks on user devices
|
||||
- ❌ DoS attacks on user connections
|
||||
- ❌ Issues requiring physical access
|
||||
- ❌ Lightning Network protocol issues
|
||||
- ❌ Browser security vulnerabilities
|
||||
|
||||
### Research Ethics
|
||||
- **No Disruption:** Don't interfere with live users
|
||||
- **Responsible Disclosure:** Follow our disclosure timeline
|
||||
- **No Data Harvesting:** Don't collect user communications
|
||||
- **Legal Compliance:** Follow all applicable laws
|
||||
- **Respect Privacy:** Don't attempt to break active encrypted sessions
|
||||
|
||||
## 🔄 Recent Security Updates (Version 4.0)
|
||||
|
||||
### Major Security Enhancements:
|
||||
- ✅ **Implemented 12-layer security architecture**
|
||||
- ✅ **Added Perfect Forward Secrecy with automatic key rotation**
|
||||
- ✅ **Enhanced MITM protection with ECDSA signatures**
|
||||
- ✅ **Implemented traffic obfuscation (fake traffic, padding, chunking)**
|
||||
- ✅ **Added anti-fingerprinting protection**
|
||||
- ✅ **Fixed demo session creation vulnerability**
|
||||
- ✅ **Eliminated session replay attacks**
|
||||
- ✅ **Enhanced rate limiting with cryptographic verification**
|
||||
|
||||
### Bug Fixes:
|
||||
- 🔧 **Fixed fake traffic visibility in user interface**
|
||||
- 🔧 **Resolved message processing conflicts**
|
||||
- 🔧 **Improved security layer error handling**
|
||||
- 🔧 **Enhanced session validation**
|
||||
|
||||
## 📚 Security Resources
|
||||
|
||||
### Technical Documentation:
|
||||
- [12-Layer Security Architecture](docs/SECURITY-ARCHITECTURE.md)
|
||||
- [Cryptographic Implementation](docs/CRYPTOGRAPHY.md)
|
||||
- [P2P Security Model](docs/P2P-SECURITY.md)
|
||||
- [Lightning Integration Security](docs/LIGHTNING-SECURITY.md)
|
||||
- [Traffic Obfuscation Guide](docs/TRAFFIC-OBFUSCATION.md)
|
||||
|
||||
### External Resources:
|
||||
- [WebRTC Security Guide](https://webrtc-security.github.io/)
|
||||
- [Web Crypto API Best Practices](https://www.w3.org/TR/WebCryptoAPI/)
|
||||
- [Lightning Network Security](https://lightning.network/lightning-network-paper.pdf)
|
||||
- [NIST Cryptographic Standards](https://csrc.nist.gov/)
|
||||
|
||||
### Security Verification:
|
||||
```bash
|
||||
# Verify current security status in browser console:
|
||||
webrtcManager.getSecurityStatus()
|
||||
# Expected: { stage: 4, securityLevel: 'MAXIMUM', activeFeatures: 12 }
|
||||
npm audit
|
||||
npm test
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 📞 Contact Information
|
||||
## Limitations
|
||||
|
||||
- **Security Team:** security@SecureBit.chat
|
||||
- **General Contact:** lockbitchat@tutanota.com
|
||||
- **GitHub Issues:** https://github.com/SecureBitChat/securebit-chat/issues
|
||||
|
||||
## 🏅 Security Achievements
|
||||
|
||||
SecureBit.chat v4.0 provides:
|
||||
- **🥇 Military-Grade Security:** 12-layer protection system
|
||||
- **🥇 Government-Level Encryption:** Triple AES-256-GCM + P-384 ECDH/ECDSA
|
||||
- **🥇 Perfect Forward Secrecy:** Complete with automatic key rotation
|
||||
- **🥇 Traffic Analysis Protection:** Maximum with 6-layer obfuscation
|
||||
- **🥇 Zero-Trust Architecture:** No central points of failure
|
||||
|
||||
**Security Rating: MAXIMUM** - Exceeds most government and military communication standards.
|
||||
|
||||
---
|
||||
|
||||
*This security policy is reviewed and updated quarterly. Last updated: January 14, 2025*
|
||||
*Security implementation verified and tested as of Version 4.0*
|
||||
- A compromised endpoint can still expose plaintext.
|
||||
- WebRTC privacy depends on deployment configuration; TURN must be supplied by the operator.
|
||||
- Users must perform the out-of-band SAS comparison correctly.
|
||||
- Browser security and operating-system security remain part of the threat model.
|
||||
|
||||
@@ -1,208 +1,27 @@
|
||||
# Security Disclaimer and Terms of Use
|
||||
|
||||
## 🔒 SecureBit.chat Enhanced Security Edition v4.01.222
|
||||
SecureBit.chat is provided as open-source software for lawful private communication, research, and education. It is supplied **as is**, without warranties of any kind.
|
||||
|
||||
### Important Legal Notice
|
||||
## User responsibilities
|
||||
|
||||
**READ THIS DISCLAIMER CAREFULLY BEFORE USING SECUREBIT.CHAT SOFTWARE**
|
||||
By using SecureBit.chat, you are responsible for:
|
||||
|
||||
---
|
||||
- complying with applicable laws and organizational policies
|
||||
- securing your devices and browser environment
|
||||
- verifying SAS codes through an out-of-band channel
|
||||
- understanding that endpoint compromise can defeat application-layer protections
|
||||
- configuring TURN correctly when relay-only privacy mode is required
|
||||
|
||||
## 📋 Overview
|
||||
## Security limitations
|
||||
|
||||
SecureBit.chat is an open-source, peer-to-peer encrypted messaging application designed to support **freedom of speech** and **privacy rights**. This software implements military-grade cryptography and is provided as-is for educational, research, and legitimate communication purposes.
|
||||
No communication system can guarantee absolute security. SecureBit.chat reduces risk through encrypted transport, mandatory peer verification, explicit file-transfer consent, local metadata protection, and lifecycle cleanup, but it cannot protect against compromised devices, malicious users with physical access, or incorrect operational practices.
|
||||
|
||||
---
|
||||
## Intended use
|
||||
|
||||
## ⚖️ Legal Disclaimer
|
||||
SecureBit.chat is intended for legitimate private communication, journalism, research, education, business confidentiality, and personal privacy. It is not intended to facilitate unlawful activity, abuse, harassment, or harm.
|
||||
|
||||
### Developer Liability
|
||||
## Current release
|
||||
|
||||
**THE DEVELOPER(S) OF SECUREBIT.CHAT ASSUME NO RESPONSIBILITY OR LIABILITY FOR:**
|
||||
|
||||
- Any misuse, illegal use, or criminal activities conducted using this software
|
||||
- Compliance with local, national, or international laws and regulations
|
||||
- Any damages, losses, or consequences resulting from the use of this software
|
||||
- The security or privacy of communications in jurisdictions where encryption is restricted
|
||||
- Any vulnerability, security flaw, or compromise that may occur despite our security measures
|
||||
|
||||
### User Responsibility
|
||||
|
||||
**BY USING SECUREBIT.CHAT, YOU ACKNOWLEDGE AND AGREE THAT:**
|
||||
|
||||
1. **Full Legal Responsibility**: You bear complete and sole responsibility for how you use this software
|
||||
2. **Compliance Obligation**: You must ensure your use complies with all applicable laws in your jurisdiction
|
||||
3. **Risk Acceptance**: You understand and accept all risks associated with using encrypted communication software
|
||||
4. **No Warranty**: This software is provided "AS IS" without any warranties, express or implied
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Jurisdictional Considerations
|
||||
|
||||
### Encryption Laws Vary Globally
|
||||
|
||||
- **Some countries restrict or prohibit** the use of strong encryption
|
||||
- **Export controls** may apply in certain jurisdictions
|
||||
- **Corporate/government networks** may have policies against encrypted communications
|
||||
- **Users must verify** local laws before using this software
|
||||
|
||||
### High-Risk Jurisdictions
|
||||
|
||||
**Exercise extreme caution** in countries with:
|
||||
- Restrictions on encrypted communications
|
||||
- Surveillance laws requiring backdoors
|
||||
- Penalties for using VPN/encryption software
|
||||
- Authoritarian internet controls
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Intended Use Cases
|
||||
|
||||
### ✅ Legitimate Uses (Encouraged)
|
||||
- **Journalism**: Protecting sources and whistleblowers
|
||||
- **Human Rights**: Organizing and advocacy in oppressive regimes
|
||||
- **Business**: Corporate communications requiring confidentiality
|
||||
- **Personal Privacy**: Private communications between individuals
|
||||
- **Research**: Academic study of cryptographic protocols
|
||||
- **Education**: Learning about secure communication systems
|
||||
|
||||
### ❌ Prohibited Uses (Illegal/Unethical)
|
||||
- Any illegal activities under applicable law
|
||||
- Criminal conspiracies or planning illegal acts
|
||||
- Harassment, threats, or abuse of others
|
||||
- Circumventing legitimate law enforcement (where legally required)
|
||||
- Distribution of illegal content
|
||||
- Financial crimes or fraud
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Limitations
|
||||
|
||||
### No Absolute Security
|
||||
|
||||
**UNDERSTAND THAT:**
|
||||
- No cryptographic system is 100% unbreakable
|
||||
- Implementation bugs may exist despite best efforts
|
||||
- Social engineering and endpoint security remain vulnerabilities
|
||||
- Quantum computing may eventually threaten current encryption
|
||||
- Traffic analysis may reveal communication patterns
|
||||
|
||||
### User Security Responsibilities
|
||||
|
||||
**YOU MUST:**
|
||||
- Keep your devices secure and updated
|
||||
- Use strong, unique passwords
|
||||
- Verify security codes through out-of-band channels
|
||||
- Understand the risks of your communication environment
|
||||
- Follow operational security (OPSEC) best practices
|
||||
|
||||
---
|
||||
|
||||
## 🏛️ Freedom of Speech Support
|
||||
|
||||
### Our Mission
|
||||
|
||||
SecureBit.chat is developed to support:
|
||||
- **Article 19** of the Universal Declaration of Human Rights
|
||||
- **Freedom of expression** and **right to privacy**
|
||||
- **Resistance to censorship** and mass surveillance
|
||||
- **Protection of journalists, activists, and dissidents**
|
||||
|
||||
### Ethical Use Commitment
|
||||
|
||||
We believe privacy and free speech are fundamental human rights, but:
|
||||
- These rights come with responsibilities
|
||||
- Freedom of speech does not include freedom from consequences
|
||||
- Users must respect the rights and safety of others
|
||||
- Illegal activity is never justified, regardless of privacy tools used
|
||||
|
||||
---
|
||||
|
||||
## 📊 Technical Security Information
|
||||
|
||||
### Current Implementation
|
||||
- **ECDH P-384** key exchange
|
||||
- **AES-GCM 256-bit** encryption
|
||||
- **ECDSA P-384** digital signatures
|
||||
- **Perfect Forward Secrecy** with key rotation
|
||||
- **MITM protection** via out-of-band verification
|
||||
- **Zero server architecture** (pure P2P)
|
||||
|
||||
### Known Limitations
|
||||
- WebRTC fingerprinting possibilities
|
||||
- Browser-based implementation constraints
|
||||
- Dependency on Web Crypto API security
|
||||
- No protection against compromised endpoints
|
||||
- Traffic analysis potential despite encryption
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Future Development
|
||||
|
||||
### Post-Quantum Roadmap
|
||||
- **v5.0**: CRYSTALS-Kyber/Dilithium implementation
|
||||
- **Long-term**: Resistance to quantum cryptanalysis
|
||||
- **Ongoing**: Security audits and improvements
|
||||
|
||||
---
|
||||
|
||||
## 📞 Contact and Reporting
|
||||
|
||||
### Security Issues
|
||||
- **Responsible disclosure**: Email security issues to the development team
|
||||
- **CVE reporting**: We participate in responsible vulnerability disclosure
|
||||
- **Bug bounty**: Consider implementing for critical security findings
|
||||
|
||||
### Legal Concerns
|
||||
- **Law enforcement**: Contact appropriate legal authorities in your jurisdiction
|
||||
- **Abuse reports**: Report illegal use to relevant authorities
|
||||
- **Compliance questions**: Consult with legal counsel
|
||||
|
||||
---
|
||||
|
||||
## 📜 License and Terms
|
||||
|
||||
### Open Source License
|
||||
SecureBit.chat is released under the **MIT License**, providing:
|
||||
- Freedom to use, modify, and distribute
|
||||
- No warranty or liability guarantees
|
||||
- Full source code transparency
|
||||
- Right to audit security implementation
|
||||
|
||||
### Terms Acceptance
|
||||
**By downloading, installing, or using SecureBit.chat, you acknowledge:**
|
||||
|
||||
1. You have read and understood this disclaimer
|
||||
2. You accept full responsibility for your use of the software
|
||||
3. You agree to comply with all applicable laws
|
||||
4. You understand the security limitations and risks
|
||||
5. You will not hold the developers liable for any consequences
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Final Warning
|
||||
|
||||
**SECUREBIT.CHAT IS A POWERFUL TOOL FOR PRIVACY AND FREE SPEECH**
|
||||
|
||||
With great power comes great responsibility. Use this software ethically, legally, and with full understanding of the risks and responsibilities involved.
|
||||
|
||||
**Remember**: The strongest encryption cannot protect against poor operational security, compromised endpoints, or illegal activities that attract law enforcement attention.
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Declaration of Intent
|
||||
|
||||
This software is created to:
|
||||
- **Protect human rights** and fundamental freedoms
|
||||
- **Support legitimate privacy** needs in an increasingly surveilled world
|
||||
- **Advance the field** of secure communications
|
||||
- **Educate users** about cryptography and privacy
|
||||
|
||||
**It is NOT intended to facilitate illegal activities or harm others.**
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 08.07.2025*
|
||||
*Version: Enhanced Security Edition v4.01.222*
|
||||
|
||||
**USE AT YOUR OWN RISK AND RESPONSIBILITY**
|
||||
- Product release: `v4.8.5`
|
||||
- Protocol version: `4.1`
|
||||
- Last updated: May 17, 2026
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Notification Sound Asset
|
||||
#
|
||||
# This file should contain a short, pleasant notification sound in MP3 format.
|
||||
# Recommended specifications:
|
||||
# - Duration: 1-2 seconds
|
||||
# - Format: MP3, 44.1kHz, 128kbps
|
||||
# - Volume: Moderate (not too loud)
|
||||
# - License: Ensure proper licensing for commercial use
|
||||
#
|
||||
# You can create this using:
|
||||
# 1. Text-to-speech generators
|
||||
# 2. Audio editing software
|
||||
# 3. Free notification sound libraries
|
||||
# 4. AI-generated sounds
|
||||
#
|
||||
# Example sources:
|
||||
# - Freesound.org (CC licensed)
|
||||
# - Zapsplat.com
|
||||
# - Adobe Audition
|
||||
# - Audacity (free)
|
||||
@@ -0,0 +1,36 @@
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/inter/v19/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuOKfMZg.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/inter/v19/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfMZg.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/inter/v19/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuI6fMZg.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/inter/v19/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuGKYMZg.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/inter/v19/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuFuYMZg.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# SecureBit.chat Build Script
|
||||
# PowerShell script for building the application
|
||||
|
||||
Write-Host "🔨 Building SecureBit.chat..." -ForegroundColor Green
|
||||
|
||||
# Check if Node.js is installed
|
||||
if (!(Get-Command node -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "❌ Node.js is not installed. Please install Node.js first." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if npm is installed
|
||||
if (!(Get-Command npm -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "❌ npm is not installed. Please install npm first." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Install dependencies if needed
|
||||
if (!(Test-Path "node_modules")) {
|
||||
Write-Host "📦 Installing dependencies..." -ForegroundColor Yellow
|
||||
npm install
|
||||
}
|
||||
|
||||
# Install build tools if needed
|
||||
Write-Host "🛠️ Installing build tools..." -ForegroundColor Yellow
|
||||
npm install -D tailwindcss esbuild
|
||||
npm install qrcode
|
||||
|
||||
# Create directories if they don't exist
|
||||
if (!(Test-Path "dist")) { New-Item -ItemType Directory -Path "dist" }
|
||||
if (!(Test-Path "assets")) { New-Item -ItemType Directory -Path "assets" }
|
||||
|
||||
# Build CSS
|
||||
Write-Host "🎨 Building Tailwind CSS..." -ForegroundColor Cyan
|
||||
try {
|
||||
npx tailwindcss -i src/styles/tw-input.css -o assets/tailwind.css --minify --content "./index.html,./src/**/*.jsx,./src/**/*.js"
|
||||
Write-Host "✅ CSS build completed" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "❌ CSS build failed: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Build JavaScript files
|
||||
Write-Host "⚡ Building JavaScript files..." -ForegroundColor Cyan
|
||||
|
||||
# Build main app
|
||||
try {
|
||||
npx esbuild src/app.jsx --bundle --format=esm --outfile=dist/app.js --sourcemap
|
||||
Write-Host "✅ Main app build completed" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "❌ Main app build failed: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Build app bootstrap
|
||||
try {
|
||||
npx esbuild src/scripts/app-boot.js --bundle --format=esm --outfile=dist/app-boot.js --sourcemap
|
||||
Write-Host "✅ App bootstrap build completed" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "❌ App bootstrap build failed: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Build QR generator
|
||||
try {
|
||||
npx esbuild src/scripts/qr-local.js --bundle --format=esm --outfile=dist/qr-local.js --sourcemap
|
||||
Write-Host "✅ QR generator build completed" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "❌ QR generator build failed: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "🎉 Build completed successfully!" -ForegroundColor Green
|
||||
Write-Host "📁 Output files:" -ForegroundColor Yellow
|
||||
Write-Host " - assets/tailwind.css" -ForegroundColor White
|
||||
Write-Host " - dist/app.js" -ForegroundColor White
|
||||
Write-Host " - dist/app-boot.js" -ForegroundColor White
|
||||
Write-Host " - dist/qr-local.js" -ForegroundColor White
|
||||
|
||||
Write-Host "`n🚀 You can now serve the application with:" -ForegroundColor Cyan
|
||||
Write-Host " python -m http.server 8000" -ForegroundColor White
|
||||
Write-Host " or" -ForegroundColor White
|
||||
Write-Host " npx http-server" -ForegroundColor White
|
||||
@@ -1,859 +1,41 @@
|
||||
# SecureBit.chat API Documentation
|
||||
# API Notes
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
## EnhancedSecureWebRTCManager
|
||||
|
||||
SecureBit.chat is built as a client-side application with no backend servers. The "API" consists of JavaScript classes and methods that handle cryptography, P2P connections, and Lightning Network integration.
|
||||
### Verification
|
||||
|
||||
## 📚 Core Classes
|
||||
- `confirmVerification(userCode)` validates a manually entered SAS code.
|
||||
- Verification succeeds only after both local and remote confirmations are present.
|
||||
- Protocol version `4.1` is enforced during offer/answer processing.
|
||||
|
||||
### 🔐 EnhancedSecureCryptoUtils
|
||||
### Privacy mode
|
||||
|
||||
Central cryptographic utilities class providing military-grade encryption.
|
||||
- relay-only configuration sets WebRTC `iceTransportPolicy` to `"relay"`.
|
||||
- TURN availability is checked before claiming IP protection.
|
||||
|
||||
#### Key Generation
|
||||
### File transfer callbacks
|
||||
|
||||
##### `generateECDHKeyPair()`
|
||||
``javascript
|
||||
static async generateECDHKeyPair(): Promise<CryptoKeyPair>
|
||||
Generates non-extractable ECDH P-384 key pair for secure key exchange.
|
||||
Returns: CryptoKeyPair with P-384 keys
|
||||
Throws: Error if key generation fails
|
||||
Example:
|
||||
javascriptconst keyPair = await EnhancedSecureCryptoUtils.generateECDHKeyPair();
|
||||
console.log(keyPair.privateKey.algorithm.namedCurve); // "P-384"
|
||||
generateECDSAKeyPair()
|
||||
javascriptstatic async generateECDSAKeyPair(): Promise<CryptoKeyPair>
|
||||
Generates non-extractable ECDSA P-384 key pair for digital signatures.
|
||||
Returns: CryptoKeyPair for signing and verification
|
||||
Throws: Error if key generation fails
|
||||
Encryption/Decryption
|
||||
encryptMessage()
|
||||
javascriptstatic async encryptMessage(
|
||||
message: string,
|
||||
encryptionKey: CryptoKey,
|
||||
macKey: CryptoKey,
|
||||
metadataKey: CryptoKey,
|
||||
messageId: string,
|
||||
sequenceNumber: number = 0
|
||||
): Promise<EncryptedMessage>
|
||||
Encrypts a message with metadata protection and sequence numbers.
|
||||
Parameters:
|
||||
- `setFileTransferCallbacks(onProgress, onReceived, onError, onIncomingRequest)` updates manager fields and any live `EnhancedSecureFileTransfer` instance.
|
||||
- Passing `null` values detaches callbacks from the active transfer system.
|
||||
|
||||
message - Plaintext message (max 2000 chars)
|
||||
encryptionKey - AES-GCM 256-bit key
|
||||
macKey - HMAC key for authentication
|
||||
metadataKey - Key for metadata encryption
|
||||
messageId - Unique message identifier
|
||||
sequenceNumber - Message sequence for replay protection
|
||||
## EnhancedSecureFileTransfer
|
||||
|
||||
Returns:
|
||||
typescriptinterface EncryptedMessage {
|
||||
messageIv: number[];
|
||||
messageData: number[];
|
||||
metadataIv: number[];
|
||||
metadataData: number[];
|
||||
mac: number[];
|
||||
version: string;
|
||||
}
|
||||
Example:
|
||||
javascriptconst encrypted = await EnhancedSecureCryptoUtils.encryptMessage(
|
||||
"Hello, secure world!",
|
||||
encryptionKey,
|
||||
macKey,
|
||||
metadataKey,
|
||||
"msg_12345",
|
||||
42
|
||||
);
|
||||
decryptMessage()
|
||||
javascriptstatic async decryptMessage(
|
||||
encryptedPayload: EncryptedMessage,
|
||||
encryptionKey: CryptoKey,
|
||||
macKey: CryptoKey,
|
||||
metadataKey: CryptoKey,
|
||||
expectedSequenceNumber?: number
|
||||
): Promise<DecryptedMessage>
|
||||
Decrypts and verifies an encrypted message.
|
||||
Returns:
|
||||
typescriptinterface DecryptedMessage {
|
||||
message: string;
|
||||
messageId: string;
|
||||
timestamp: number;
|
||||
sequenceNumber: number;
|
||||
}
|
||||
Key Exchange
|
||||
deriveSharedKeys()
|
||||
javascriptstatic async deriveSharedKeys(
|
||||
privateKey: CryptoKey,
|
||||
publicKey: CryptoKey,
|
||||
salt: Uint8Array
|
||||
): Promise<SharedKeys>
|
||||
Derives shared encryption keys using ECDH + HKDF.
|
||||
Parameters:
|
||||
### Incoming transfers
|
||||
|
||||
privateKey - Local ECDH private key
|
||||
publicKey - Remote ECDH public key
|
||||
salt - 64-byte cryptographic salt
|
||||
- metadata is validated before prompting
|
||||
- acceptance is explicit
|
||||
- receive buffers are allocated only after consent
|
||||
- file type acceptance is allowlist-based
|
||||
|
||||
Returns:
|
||||
typescriptinterface SharedKeys {
|
||||
encryptionKey: CryptoKey;
|
||||
macKey: CryptoKey;
|
||||
metadataKey: CryptoKey;
|
||||
fingerprint: string;
|
||||
timestamp: number;
|
||||
version: string;
|
||||
}
|
||||
Example:
|
||||
javascriptconst salt = EnhancedSecureCryptoUtils.generateSalt();
|
||||
const sharedKeys = await EnhancedSecureCryptoUtils.deriveSharedKeys(
|
||||
localPrivateKey,
|
||||
remotePublicKey,
|
||||
salt
|
||||
);
|
||||
console.log('Key fingerprint:', sharedKeys.fingerprint);
|
||||
Digital Signatures
|
||||
signData()
|
||||
javascriptstatic async signData(
|
||||
privateKey: CryptoKey,
|
||||
data: string | Uint8Array
|
||||
): Promise<number[]>
|
||||
Signs data with ECDSA P-384.
|
||||
Parameters:
|
||||
### Cleanup
|
||||
|
||||
privateKey - ECDSA private key
|
||||
data - Data to sign
|
||||
- pending sender consent promises are rejected on cleanup
|
||||
- consent timeouts are cleared immediately
|
||||
- retained received buffers are bounded
|
||||
- evicted download handles fail with a user-facing availability message
|
||||
|
||||
Returns: Signature as byte array
|
||||
Example:
|
||||
javascriptconst signature = await EnhancedSecureCryptoUtils.signData(
|
||||
ecdsaPrivateKey,
|
||||
"Important message"
|
||||
);
|
||||
verifySignature()
|
||||
javascriptstatic async verifySignature(
|
||||
publicKey: CryptoKey,
|
||||
signature: number[],
|
||||
data: string | Uint8Array
|
||||
): Promise<boolean>
|
||||
Verifies ECDSA signature.
|
||||
Returns: true if signature is valid
|
||||
Authentication
|
||||
generateMutualAuthChallenge()
|
||||
javascriptstatic generateMutualAuthChallenge(): AuthChallenge
|
||||
Generates cryptographic challenge for mutual authentication.
|
||||
Returns:
|
||||
typescriptinterface AuthChallenge {
|
||||
challenge: number[];
|
||||
timestamp: number;
|
||||
nonce: number[];
|
||||
version: string;
|
||||
}
|
||||
createAuthProof()
|
||||
javascriptstatic async createAuthProof(
|
||||
challenge: AuthChallenge,
|
||||
privateKey: CryptoKey,
|
||||
publicKey: CryptoKey
|
||||
): Promise<AuthProof>
|
||||
Creates cryptographic proof for challenge response.
|
||||
Returns:
|
||||
typescriptinterface AuthProof {
|
||||
challenge: number[];
|
||||
timestamp: number;
|
||||
nonce: number[];
|
||||
responseTimestamp: number;
|
||||
publicKeyHash: string;
|
||||
signature: number[];
|
||||
version: string;
|
||||
}
|
||||
verifyAuthProof()
|
||||
javascriptstatic async verifyAuthProof(
|
||||
proof: AuthProof,
|
||||
challenge: AuthChallenge,
|
||||
publicKey: CryptoKey
|
||||
): Promise<boolean>
|
||||
Verifies authentication proof against challenge.
|
||||
Utility Functions
|
||||
generateSalt()
|
||||
javascriptstatic generateSalt(): number[]
|
||||
Generates 64-byte cryptographically secure salt.
|
||||
sanitizeMessage()
|
||||
javascriptstatic sanitizeMessage(message: string): string
|
||||
Sanitizes user input to prevent XSS attacks.
|
||||
Example:
|
||||
javascriptconst clean = EnhancedSecureCryptoUtils.sanitizeMessage("<script>alert('xss')</script>Hello");
|
||||
// Returns: "Hello"
|
||||
calculateSecurityLevel()
|
||||
javascriptstatic async calculateSecurityLevel(securityManager: any): Promise<SecurityLevel>
|
||||
Calculates real-time security level based on active protections.
|
||||
Returns:
|
||||
typescriptinterface SecurityLevel {
|
||||
level: 'HIGH' | 'MEDIUM' | 'LOW' | 'UNKNOWN';
|
||||
score: number; // 0-100
|
||||
color: 'green' | 'yellow' | 'red';
|
||||
verificationResults: Record<string, VerificationResult>;
|
||||
timestamp: number;
|
||||
details: string;
|
||||
}
|
||||
## SecurePersistentKeyStorage
|
||||
|
||||
interface VerificationResult {
|
||||
passed: boolean;
|
||||
details: string;
|
||||
}
|
||||
generateVerificationCode()
|
||||
javascriptstatic generateVerificationCode(): string
|
||||
Generates 6-character verification code for out-of-band authentication.
|
||||
Returns: Code in format "AB-CD-EF"
|
||||
calculateKeyFingerprint()
|
||||
javascriptstatic async calculateKeyFingerprint(keyData: number[]): Promise<string>
|
||||
Calculates SHA-256 fingerprint of key data for MITM protection.
|
||||
encryptData() / decryptData()
|
||||
javascriptstatic async encryptData(data: any, password: string): Promise<string>
|
||||
static async decryptData(encryptedData: string, password: string): Promise<any>
|
||||
High-level encryption/decryption for offer/answer exchange.
|
||||
Example:
|
||||
javascriptconst password = EnhancedSecureCryptoUtils.generateSecurePassword();
|
||||
const encrypted = await EnhancedSecureCryptoUtils.encryptData(
|
||||
{ message: "secret data" },
|
||||
password
|
||||
);
|
||||
const decrypted = await EnhancedSecureCryptoUtils.decryptData(encrypted, password);
|
||||
🌐 EnhancedSecureWebRTCManager
|
||||
Manages P2P connections with enhanced security features.
|
||||
Constructor
|
||||
javascriptnew EnhancedSecureWebRTCManager(
|
||||
onMessage: (message: string, type: string) => void,
|
||||
onStatusChange: (status: string) => void,
|
||||
onKeyExchange: (fingerprint: string) => void,
|
||||
onVerificationRequired: (code: string) => void
|
||||
)
|
||||
Parameters:
|
||||
|
||||
onMessage - Callback for received messages
|
||||
onStatusChange - Callback for connection state changes
|
||||
onKeyExchange - Callback when keys are exchanged
|
||||
onVerificationRequired - Callback when verification code is generated
|
||||
|
||||
Connection Management
|
||||
createSecureOffer()
|
||||
javascriptasync createSecureOffer(): Promise<SecureOffer>
|
||||
Creates encrypted connection offer with ECDH keys and authentication.
|
||||
Returns:
|
||||
typescriptinterface SecureOffer {
|
||||
type: 'enhanced_secure_offer';
|
||||
sdp: string;
|
||||
ecdhPublicKey: SignedPublicKey;
|
||||
ecdsaPublicKey: SignedPublicKey;
|
||||
salt: number[];
|
||||
verificationCode: string;
|
||||
authChallenge: AuthChallenge;
|
||||
sessionId: string;
|
||||
timestamp: number;
|
||||
version: string;
|
||||
securityLevel: SecurityLevel;
|
||||
}
|
||||
|
||||
interface SignedPublicKey {
|
||||
keyType: 'ECDH' | 'ECDSA';
|
||||
keyData: number[];
|
||||
timestamp: number;
|
||||
version: string;
|
||||
signature: number[];
|
||||
}
|
||||
Example:
|
||||
javascriptconst webrtcManager = new EnhancedSecureWebRTCManager(/*...*/);
|
||||
const offer = await webrtcManager.createSecureOffer();
|
||||
console.log('Verification code:', offer.verificationCode);
|
||||
createSecureAnswer()
|
||||
javascriptasync createSecureAnswer(offerData: SecureOffer): Promise<SecureAnswer>
|
||||
Creates encrypted response to connection offer.
|
||||
Returns:
|
||||
typescriptinterface SecureAnswer {
|
||||
type: 'enhanced_secure_answer';
|
||||
sdp: string;
|
||||
ecdhPublicKey: SignedPublicKey;
|
||||
ecdsaPublicKey: SignedPublicKey;
|
||||
authProof: AuthProof;
|
||||
timestamp: number;
|
||||
version: string;
|
||||
securityLevel: SecurityLevel;
|
||||
}
|
||||
handleSecureAnswer()
|
||||
javascriptasync handleSecureAnswer(answerData: SecureAnswer): Promise<void>
|
||||
Processes encrypted answer and establishes connection.
|
||||
Throws: Error if answer is invalid or authentication fails
|
||||
Message Handling
|
||||
sendSecureMessage()
|
||||
javascriptasync sendSecureMessage(message: string): Promise<void>
|
||||
Sends encrypted message through secure channel.
|
||||
Parameters:
|
||||
|
||||
message - Plaintext message (auto-sanitized)
|
||||
|
||||
Features:
|
||||
|
||||
Automatic encryption with metadata protection
|
||||
Sequence number tracking
|
||||
Rate limiting (60 messages/minute)
|
||||
Perfect Forward Secrecy key rotation
|
||||
|
||||
Example:
|
||||
javascriptawait webrtcManager.sendSecureMessage("Hello, secure world!");
|
||||
Connection States
|
||||
typescripttype ConnectionState =
|
||||
| 'disconnected' // No connection
|
||||
| 'connecting' // Establishing connection
|
||||
| 'verifying' // Verifying security codes
|
||||
| 'connected' // Fully connected and verified
|
||||
| 'failed' // Connection failed
|
||||
| 'reconnecting' // Attempting to reconnect
|
||||
| 'peer_disconnected'; // Peer disconnected
|
||||
Security Features
|
||||
calculateSecurityLevel()
|
||||
javascriptasync calculateSecurityLevel(): Promise<SecurityLevel>
|
||||
Real-time security assessment with verification of:
|
||||
|
||||
✅ Encryption functionality
|
||||
✅ ECDH key exchange
|
||||
✅ ECDSA signatures
|
||||
✅ Mutual authentication
|
||||
✅ Metadata protection
|
||||
✅ Replay protection
|
||||
✅ Non-extractable keys
|
||||
✅ Rate limiting
|
||||
✅ Perfect Forward Secrecy
|
||||
|
||||
shouldRotateKeys()
|
||||
javascriptshouldRotateKeys(): boolean
|
||||
Determines if PFS key rotation is needed (every 5 minutes or 100 messages).
|
||||
isConnected()
|
||||
javascriptisConnected(): boolean
|
||||
Returns true if WebRTC data channel is open and ready.
|
||||
getConnectionInfo()
|
||||
javascriptgetConnectionInfo(): ConnectionInfo
|
||||
Returns:
|
||||
typescriptinterface ConnectionInfo {
|
||||
fingerprint: string;
|
||||
isConnected: boolean;
|
||||
isVerified: boolean;
|
||||
connectionState: string;
|
||||
iceConnectionState: string;
|
||||
verificationCode: string;
|
||||
}
|
||||
Perfect Forward Secrecy
|
||||
rotateKeys()
|
||||
javascriptasync rotateKeys(): Promise<boolean>
|
||||
Performs key rotation for Perfect Forward Secrecy.
|
||||
Returns: true if rotation successful
|
||||
getKeysForVersion()
|
||||
javascriptgetKeysForVersion(version: number): KeySet | null
|
||||
Retrieves keys for specific version (for decrypting old messages).
|
||||
Returns:
|
||||
typescriptinterface KeySet {
|
||||
encryptionKey: CryptoKey;
|
||||
macKey: CryptoKey;
|
||||
metadataKey: CryptoKey;
|
||||
}
|
||||
Connection Control
|
||||
disconnect()
|
||||
javascriptdisconnect(): void
|
||||
Cleanly disconnects and cleans up all resources.
|
||||
confirmVerification()
|
||||
javascriptconfirmVerification(): void
|
||||
Confirms that verification codes match (called after manual verification).
|
||||
⚡ PayPerSessionManager
|
||||
Handles Lightning Network payment integration.
|
||||
Constructor
|
||||
javascriptnew PayPerSessionManager()
|
||||
Session Types
|
||||
typescriptinterface SessionPricing {
|
||||
free: { sats: 0, hours: 1/60, usd: 0.00 };
|
||||
basic: { sats: 500, hours: 1, usd: 0.20 };
|
||||
premium: { sats: 1000, hours: 4, usd: 0.40 };
|
||||
extended: { sats: 2000, hours: 24, usd: 0.80 };
|
||||
}
|
||||
Payment Methods
|
||||
createInvoice()
|
||||
javascriptcreateInvoice(sessionType: string): LightningInvoice
|
||||
Creates Lightning invoice for session payment.
|
||||
Parameters:
|
||||
|
||||
sessionType - One of: 'free', 'basic', 'premium', 'extended'
|
||||
|
||||
Returns:
|
||||
typescriptinterface LightningInvoice {
|
||||
amount: number; // satoshis
|
||||
memo: string;
|
||||
sessionType: string;
|
||||
timestamp: number;
|
||||
paymentHash: string;
|
||||
lightningAddress: string;
|
||||
}
|
||||
Example:
|
||||
javascriptconst sessionManager = new PayPerSessionManager();
|
||||
const invoice = sessionManager.createInvoice('premium');
|
||||
console.log(`Pay ${invoice.amount} sats to ${invoice.lightningAddress}`);
|
||||
verifyPayment()
|
||||
javascriptasync verifyPayment(preimage: string, paymentHash: string): Promise<boolean>
|
||||
Verifies Lightning payment preimage.
|
||||
Parameters:
|
||||
|
||||
preimage - Payment preimage (64 hex characters)
|
||||
paymentHash - Payment hash from invoice
|
||||
|
||||
Returns: true if payment is valid
|
||||
activateSession()
|
||||
javascriptactivateSession(sessionType: string, preimage: string): Session
|
||||
Activates paid session.
|
||||
Returns:
|
||||
typescriptinterface Session {
|
||||
type: string;
|
||||
startTime: number;
|
||||
expiresAt: number;
|
||||
preimage: string;
|
||||
}
|
||||
Session Management
|
||||
hasActiveSession()
|
||||
javascripthasActiveSession(): boolean
|
||||
Returns true if there's an active, non-expired session.
|
||||
getTimeLeft()
|
||||
javascriptgetTimeLeft(): number
|
||||
Returns milliseconds remaining in current session.
|
||||
Example:
|
||||
javascriptconst timeLeft = sessionManager.getTimeLeft();
|
||||
const hoursLeft = Math.floor(timeLeft / (1000 * 60 * 60));
|
||||
console.log(`${hoursLeft} hours remaining`);
|
||||
cleanup()
|
||||
javascriptcleanup(): void
|
||||
Cleans up session data and timers.
|
||||
🔧 Integration Examples
|
||||
Basic P2P Chat Setup
|
||||
javascript// Initialize WebRTC manager
|
||||
const webrtcManager = new EnhancedSecureWebRTCManager(
|
||||
(message, type) => {
|
||||
console.log(`${type}: ${message}`);
|
||||
addMessageToUI(message, type);
|
||||
},
|
||||
(status) => {
|
||||
console.log(`Status: ${status}`);
|
||||
updateStatusIndicator(status);
|
||||
},
|
||||
(fingerprint) => {
|
||||
console.log(`Key fingerprint: ${fingerprint}`);
|
||||
displayFingerprint(fingerprint);
|
||||
},
|
||||
(code) => {
|
||||
console.log(`Verification code: ${code}`);
|
||||
showVerificationModal(code);
|
||||
}
|
||||
);
|
||||
|
||||
// Create secure offer
|
||||
const offer = await webrtcManager.createSecureOffer();
|
||||
console.log('Share this encrypted offer:', JSON.stringify(offer));
|
||||
|
||||
// Send message (after connection established)
|
||||
await webrtcManager.sendSecureMessage('Hello, secure world!');
|
||||
Lightning Payment Integration
|
||||
javascript// Initialize session manager
|
||||
const sessionManager = new PayPerSessionManager();
|
||||
|
||||
// Create invoice for premium session
|
||||
const invoice = sessionManager.createInvoice('premium');
|
||||
console.log(`Pay ${invoice.amount} sats to: ${invoice.lightningAddress}`);
|
||||
|
||||
// Handle payment (WebLN)
|
||||
if (window.webln) {
|
||||
try {
|
||||
await window.webln.enable();
|
||||
const result = await window.webln.sendPayment({
|
||||
amount: invoice.amount,
|
||||
memo: invoice.memo
|
||||
});
|
||||
|
||||
// Verify and activate session
|
||||
const isValid = await sessionManager.verifyPayment(
|
||||
result.preimage,
|
||||
invoice.paymentHash
|
||||
);
|
||||
|
||||
if (isValid) {
|
||||
const session = sessionManager.activateSession('premium', result.preimage);
|
||||
console.log(`Session active until: ${new Date(session.expiresAt)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('WebLN payment failed:', error);
|
||||
}
|
||||
}
|
||||
Custom Cryptographic Operations
|
||||
javascript// Generate fresh key pairs
|
||||
const ecdhKeys = await EnhancedSecureCryptoUtils.generateECDHKeyPair();
|
||||
const ecdsaKeys = await EnhancedSecureCryptoUtils.generateECDSAKeyPair();
|
||||
|
||||
// Create and verify signature
|
||||
const data = 'Important message to sign';
|
||||
const signature = await EnhancedSecureCryptoUtils.signData(
|
||||
ecdsaKeys.privateKey,
|
||||
data
|
||||
);
|
||||
|
||||
const isValid = await EnhancedSecureCryptoUtils.verifySignature(
|
||||
ecdsaKeys.publicKey,
|
||||
signature,
|
||||
data
|
||||
);
|
||||
console.log('Signature valid:', isValid);
|
||||
|
||||
// Derive shared keys
|
||||
const salt = EnhancedSecureCryptoUtils.generateSalt();
|
||||
const sharedKeys = await EnhancedSecureCryptoUtils.deriveSharedKeys(
|
||||
ecdhKeys.privateKey,
|
||||
remotePublicKey,
|
||||
salt
|
||||
);
|
||||
|
||||
// Encrypt message
|
||||
const encrypted = await EnhancedSecureCryptoUtils.encryptMessage(
|
||||
"Secret message",
|
||||
sharedKeys.encryptionKey,
|
||||
sharedKeys.macKey,
|
||||
sharedKeys.metadataKey,
|
||||
"msg_001",
|
||||
1
|
||||
);
|
||||
Full Connection Flow
|
||||
javascript// Complete initiator flow
|
||||
async function initiatorFlow() {
|
||||
// 1. Create WebRTC manager
|
||||
const manager = new EnhancedSecureWebRTCManager(
|
||||
handleMessage,
|
||||
handleStatusChange,
|
||||
handleKeyExchange,
|
||||
handleVerification
|
||||
);
|
||||
|
||||
// 2. Create offer
|
||||
const offer = await manager.createSecureOffer();
|
||||
|
||||
// 3. Encrypt offer for sharing
|
||||
const password = EnhancedSecureCryptoUtils.generateSecurePassword();
|
||||
const encryptedOffer = await EnhancedSecureCryptoUtils.encryptData(offer, password);
|
||||
|
||||
// 4. Share encrypted offer and password with peer
|
||||
console.log('Encrypted offer:', encryptedOffer);
|
||||
console.log('Password:', password);
|
||||
|
||||
// 5. Wait for encrypted answer from peer
|
||||
const encryptedAnswer = await getAnswerFromPeer();
|
||||
const answerPassword = await getPasswordFromPeer();
|
||||
|
||||
// 6. Decrypt and process answer
|
||||
const answer = await EnhancedSecureCryptoUtils.decryptData(
|
||||
encryptedAnswer,
|
||||
answerPassword
|
||||
);
|
||||
await manager.handleSecureAnswer(answer);
|
||||
|
||||
// 7. Verify out-of-band codes
|
||||
await verifySecurityCodes();
|
||||
|
||||
// 8. Start secure communication
|
||||
await manager.sendSecureMessage("Hello from initiator!");
|
||||
}
|
||||
Responder Flow
|
||||
javascriptasync function responderFlow() {
|
||||
// 1. Get encrypted offer from initiator
|
||||
const encryptedOffer = await getOfferFromPeer();
|
||||
const offerPassword = await getPasswordFromPeer();
|
||||
|
||||
// 2. Decrypt offer
|
||||
const offer = await EnhancedSecureCryptoUtils.decryptData(
|
||||
encryptedOffer,
|
||||
offerPassword
|
||||
);
|
||||
|
||||
// 3. Create WebRTC manager
|
||||
const manager = new EnhancedSecureWebRTCManager(
|
||||
handleMessage,
|
||||
handleStatusChange,
|
||||
handleKeyExchange,
|
||||
handleVerification
|
||||
);
|
||||
|
||||
// 4. Create answer
|
||||
const answer = await manager.createSecureAnswer(offer);
|
||||
|
||||
// 5. Encrypt answer for sharing
|
||||
const password = EnhancedSecureCryptoUtils.generateSecurePassword();
|
||||
const encryptedAnswer = await EnhancedSecureCryptoUtils.encryptData(answer, password);
|
||||
|
||||
// 6. Share encrypted answer and password
|
||||
await sendAnswerToPeer(encryptedAnswer);
|
||||
await sendPasswordToPeer(password);
|
||||
|
||||
// 7. Verify out-of-band codes
|
||||
await verifySecurityCodes();
|
||||
|
||||
// 8. Start secure communication
|
||||
await manager.sendSecureMessage("Hello from responder!");
|
||||
}
|
||||
🔒 Security Considerations
|
||||
Key Security
|
||||
|
||||
All keys are non-extractable - Cannot be exported from WebCrypto
|
||||
Hardware security module - Keys protected by browser's HSM
|
||||
Perfect Forward Secrecy - Old messages stay secure even if current keys compromised
|
||||
Automatic key rotation - Keys change every 5 minutes
|
||||
|
||||
Message Security
|
||||
|
||||
Authenticated encryption - AES-GCM provides confidentiality + integrity
|
||||
Metadata protection - Message metadata separately encrypted
|
||||
Replay protection - Sequence numbers prevent message replay
|
||||
Rate limiting - Prevents spam and DoS attacks
|
||||
|
||||
Connection Security
|
||||
|
||||
Out-of-band verification - Manual code verification prevents MITM
|
||||
Mutual authentication - Both parties prove identity
|
||||
Direct P2P - No intermediate servers to compromise
|
||||
WebRTC encryption - DTLS transport layer security
|
||||
|
||||
Payment Security
|
||||
|
||||
Lightning Network - No credit card or banking data exposure
|
||||
Preimage verification - Cryptographic proof of payment
|
||||
No payment data stored - Payments verified and discarded
|
||||
|
||||
🐛 Error Handling
|
||||
Common Error Types
|
||||
typescript// Cryptographic errors
|
||||
class CryptoError extends Error {
|
||||
constructor(message: string) {
|
||||
super(`Crypto Error: ${message}`);
|
||||
this.name = 'CryptoError';
|
||||
}
|
||||
}
|
||||
|
||||
// Connection errors
|
||||
class ConnectionError extends Error {
|
||||
constructor(message: string) {
|
||||
super(`Connection Error: ${message}`);
|
||||
this.name = 'ConnectionError';
|
||||
}
|
||||
}
|
||||
|
||||
// Payment errors
|
||||
class PaymentError extends Error {
|
||||
constructor(message: string) {
|
||||
super(`Payment Error: ${message}`);
|
||||
this.name = 'PaymentError';
|
||||
}
|
||||
}
|
||||
Error Recovery Patterns
|
||||
javascript// Robust message sending with retry
|
||||
async function sendMessageWithRetry(manager, message, maxRetries = 3) {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
await manager.sendSecureMessage(message);
|
||||
return; // Success
|
||||
} catch (error) {
|
||||
console.warn(`Send attempt ${attempt} failed:`, error.message);
|
||||
|
||||
if (error.message.includes('Session expired')) {
|
||||
throw new PaymentError('Session expired - payment required');
|
||||
}
|
||||
|
||||
if (error.message.includes('Rate limit')) {
|
||||
// Wait before retry
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attempt === maxRetries) {
|
||||
throw error; // Final attempt failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connection error handling
|
||||
function handleConnectionError(error) {
|
||||
if (error.message.includes('MITM')) {
|
||||
alert('⚠️ Security threat detected! Connection terminated.');
|
||||
return 'security_threat';
|
||||
}
|
||||
|
||||
if (error.message.includes('timeout')) {
|
||||
return 'timeout';
|
||||
}
|
||||
|
||||
if (error.message.includes('ice')) {
|
||||
return 'nat_traversal';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
// Payment error handling
|
||||
function handlePaymentError(error) {
|
||||
if (error.message.includes('preimage')) {
|
||||
return 'invalid_payment';
|
||||
}
|
||||
|
||||
if (error.message.includes('expired')) {
|
||||
return 'session_expired';
|
||||
}
|
||||
|
||||
if (error.message.includes('webln')) {
|
||||
return 'webln_failed';
|
||||
}
|
||||
|
||||
return 'payment_failed';
|
||||
}
|
||||
🧪 Testing
|
||||
Unit Testing Examples
|
||||
javascript// Test encryption/decryption round-trip
|
||||
async function testEncryptionRoundTrip() {
|
||||
const originalMessage = 'Test message for encryption';
|
||||
const keys = await generateTestKeys();
|
||||
|
||||
const encrypted = await EnhancedSecureCryptoUtils.encryptMessage(
|
||||
originalMessage,
|
||||
keys.encryptionKey,
|
||||
keys.macKey,
|
||||
keys.metadataKey,
|
||||
'test-id',
|
||||
0
|
||||
);
|
||||
|
||||
const decrypted = await EnhancedSecureCryptoUtils.decryptMessage(
|
||||
encrypted,
|
||||
keys.encryptionKey,
|
||||
keys.macKey,
|
||||
keys.metadataKey
|
||||
);
|
||||
|
||||
assert.equal(decrypted.message, originalMessage);
|
||||
assert.equal(decrypted.messageId, 'test-id');
|
||||
assert.equal(decrypted.sequenceNumber, 0);
|
||||
}
|
||||
|
||||
// Test key generation
|
||||
async function testKeyGeneration() {
|
||||
const ecdhPair = await EnhancedSecureCryptoUtils.generateECDHKeyPair();
|
||||
const ecdsaPair = await EnhancedSecureCryptoUtils.generateECDSAKeyPair();
|
||||
|
||||
// Verify key properties
|
||||
assert.equal(ecdhPair.privateKey.algorithm.name, 'ECDH');
|
||||
assert.equal(ecdhPair.privateKey.algorithm.namedCurve, 'P-384');
|
||||
assert.equal(ecdhPair.privateKey.extractable, false);
|
||||
|
||||
assert.equal(ecdsaPair.privateKey.algorithm.name, 'ECDSA');
|
||||
assert.equal(ecdsaPair.privateKey.algorithm.namedCurve, 'P-384');
|
||||
assert.equal(ecdsaPair.privateKey.extractable, false);
|
||||
}
|
||||
|
||||
// Test signature verification
|
||||
async function testSignatureVerification() {
|
||||
const keyPair = await EnhancedSecureCryptoUtils.generateECDSAKeyPair();
|
||||
const data = 'Test data to sign';
|
||||
|
||||
const signature = await EnhancedSecureCryptoUtils.signData(
|
||||
keyPair.privateKey,
|
||||
data
|
||||
);
|
||||
|
||||
const isValid = await EnhancedSecureCryptoUtils.verifySignature(
|
||||
keyPair.publicKey,
|
||||
signature,
|
||||
data
|
||||
);
|
||||
|
||||
assert.equal(isValid, true);
|
||||
|
||||
// Test with wrong data
|
||||
const invalidVerification = await EnhancedSecureCryptoUtils.verifySignature(
|
||||
keyPair.publicKey,
|
||||
signature,
|
||||
'Wrong data'
|
||||
);
|
||||
|
||||
assert.equal(invalidVerification, false);
|
||||
}
|
||||
|
||||
// Helper function for tests
|
||||
async function generateTestKeys() {
|
||||
const ecdhPair = await EnhancedSecureCryptoUtils.generateECDHKeyPair();
|
||||
const salt = EnhancedSecureCryptoUtils.generateSalt();
|
||||
|
||||
// For testing, we'll create a mock "remote" key pair
|
||||
const remotePair = await EnhancedSecureCryptoUtils.generateECDHKeyPair();
|
||||
|
||||
const sharedKeys = await EnhancedSecureCryptoUtils.deriveSharedKeys(
|
||||
ecdhPair.privateKey,
|
||||
remotePair.publicKey,
|
||||
salt
|
||||
);
|
||||
|
||||
return sharedKeys;
|
||||
}
|
||||
Integration Testing
|
||||
javascript// Test full P2P connection flow
|
||||
async function testP2PConnection() {
|
||||
let manager1Messages = [];
|
||||
let manager2Messages = [];
|
||||
|
||||
const manager1 = new EnhancedSecureWebRTCManager(
|
||||
(msg, type) => manager1Messages.push({msg, type}),
|
||||
(status) => console.log('Manager1 status:', status),
|
||||
(fingerprint) => console.log('Manager1 fingerprint:', fingerprint),
|
||||
(code) => console.log('Manager1 verification:', code)
|
||||
);
|
||||
|
||||
const manager2 = new EnhancedSecureWebRTCManager(
|
||||
(msg, type) => manager2Messages.push({msg, type}),
|
||||
(status) => console.log('Manager2 status:', status),
|
||||
(fingerprint) => console.log('Manager2 fingerprint:', fingerprint),
|
||||
(code) => console.log('Manager2 verification:', code)
|
||||
);
|
||||
|
||||
// Create offer
|
||||
const offer = await manager1.createSecureOffer();
|
||||
|
||||
// Create answer
|
||||
const answer = await manager2.createSecureAnswer(offer);
|
||||
|
||||
// Handle answer
|
||||
await manager1.handleSecureAnswer(answer);
|
||||
|
||||
// Wait for connection
|
||||
await waitForConnection(manager1, manager2);
|
||||
|
||||
// Verify both are connected
|
||||
assert.equal(manager1.isConnected(), true);
|
||||
assert.equal(manager2.isConnected(), true);
|
||||
|
||||
// Test message exchange
|
||||
await manager1.sendSecureMessage('Hello from manager1');
|
||||
await manager2.sendSecureMessage('Hello from manager2');
|
||||
|
||||
// Wait for messages to arrive
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Verify messages were received
|
||||
assert.equal(manager2Messages.length > 0, true);
|
||||
assert.equal(manager1Messages.length > 0, true);
|
||||
}
|
||||
|
||||
async function waitForConnection(manager1, manager2, timeout = 10000) {
|
||||
const start = Date.now();
|
||||
|
||||
while (Date.now() - start < timeout) {
|
||||
if (manager1.isConnected() && manager2.isConnected()) {
|
||||
return;
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
throw new Error('Connection timeout');
|
||||
}
|
||||
- metadata is encrypted before storage
|
||||
- legacy plaintext records migrate lazily
|
||||
- corrupted encrypted metadata is ignored safely
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Configuration Guide
|
||||
|
||||
## Requirements
|
||||
|
||||
- modern browser with WebRTC and Web Crypto support
|
||||
- Node.js 18+ for local development
|
||||
- TURN service only when relay-only privacy mode is required
|
||||
|
||||
## Local setup
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
npm run serve
|
||||
```
|
||||
|
||||
## ICE server configuration
|
||||
|
||||
SecureBit.chat keeps existing STUN support for ordinary WebRTC connectivity. Deployments that require relay-only privacy must provide their own TURN service credentials through deployment configuration; public TURN credentials are intentionally not bundled.
|
||||
|
||||
### Privacy modes
|
||||
|
||||
| Mode | Behavior | IP privacy |
|
||||
| --- | --- | --- |
|
||||
| default | standard WebRTC candidate gathering | direct candidates may expose IP addresses |
|
||||
| relay-only | `iceTransportPolicy: "relay"` | requires TURN and avoids direct peer candidates when configured correctly |
|
||||
|
||||
### Operational rules
|
||||
|
||||
- STUN is not a privacy substitute for TURN.
|
||||
- Relay-only mode without TURN cannot establish a working relay connection.
|
||||
- The UI warns users when TURN is missing.
|
||||
- Validate TURN deployment with browser WebRTC diagnostics before production rollout.
|
||||
|
||||
## Verification flow
|
||||
|
||||
Protocol `4.1` requires interactive SAS verification:
|
||||
|
||||
1. both peers derive the same SAS from shared session material
|
||||
2. users compare the code out of band
|
||||
3. each user enters the matching code manually
|
||||
4. the chat unlocks only after both confirmations succeed
|
||||
|
||||
Three failed local attempts disconnect the session.
|
||||
|
||||
## File-transfer policy
|
||||
|
||||
Incoming file requests are validated before the consent prompt and require explicit user approval.
|
||||
|
||||
Allowed categories:
|
||||
|
||||
- common raster images
|
||||
- PDF
|
||||
- plain text
|
||||
- ZIP archives
|
||||
|
||||
Blocked examples:
|
||||
|
||||
- `.exe`, `.bat`, `.cmd`, `.sh`, `.js`
|
||||
- `.msi`, `.dmg`, `.app`, `.jar`, `.scr`
|
||||
- `.ps1`, `.vbs`, `.html`, `.svg`
|
||||
|
||||
Both MIME type and extension must be acceptable. Missing or unknown MIME types are treated as unsafe unless explicitly covered by policy.
|
||||
@@ -1,720 +1,53 @@
|
||||
# SecureBit.chat Security Architecture
|
||||
# Security Architecture
|
||||
|
||||
## 🛡️ Overview
|
||||
## Current baseline
|
||||
|
||||
SecureBit.chat implements a revolutionary **12-layer security architecture** that provides military-grade protection for peer-to-peer communications. This document details the technical implementation of our security system, which exceeds most government and enterprise communication standards.
|
||||
| Area | Current behavior |
|
||||
| --- | --- |
|
||||
| Protocol | `4.1` with mismatch rejection |
|
||||
| Peer verification | mandatory manual SAS entry |
|
||||
| Transport | WebRTC over DTLS |
|
||||
| Privacy mode | optional TURN relay-only mode |
|
||||
| Message UI safety | incoming decrypted text sanitized before display |
|
||||
| File transfer | validated metadata, explicit consent, allowlist policy |
|
||||
| Local metadata | encrypted IndexedDB envelopes with migration |
|
||||
| Lifecycle | unified disconnect cleanup and bounded resource retention |
|
||||
|
||||
**Current Implementation:** Stage 4 - Maximum Security
|
||||
**Security Rating:** Military-Grade
|
||||
**Active Layers:** 12/12
|
||||
**Threat Protection:** Comprehensive (MITM, Traffic Analysis, Replay Attacks, Session Hijacking)
|
||||
## Verification state machine
|
||||
|
||||
---
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
1. [Security Architecture Overview](#security-architecture-overview)
|
||||
2. [Layer-by-Layer Analysis](#layer-by-layer-analysis)
|
||||
3. [Cryptographic Specifications](#cryptographic-specifications)
|
||||
4. [Threat Model](#threat-model)
|
||||
5. [Implementation Details](#implementation-details)
|
||||
6. [Security Verification](#security-verification)
|
||||
7. [Performance Impact](#performance-impact)
|
||||
8. [Compliance Standards](#compliance-standards)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Security Architecture Overview
|
||||
|
||||
### 12-Layer Defense System
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ APPLICATION LAYER │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Layer 12: Perfect Forward Secrecy (Key Rotation) │
|
||||
│ Layer 11: Enhanced Rate Limiting (DDoS Protection) │
|
||||
│ Layer 10: Fake Traffic Generation (Traffic Analysis) │
|
||||
│ Layer 9: Message Chunking (Timing Analysis Protection) │
|
||||
│ Layer 8: Packet Reordering Protection (Sequence Security) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ OBFUSCATION LAYER │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Layer 7: Anti-Fingerprinting (Pattern Obfuscation) │
|
||||
│ Layer 6: Packet Padding (Size Obfuscation) │
|
||||
│ Layer 5: Nested Encryption (Additional AES-GCM) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ENCRYPTION LAYER │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Layer 4: Message Encryption (Enhanced AES-GCM) │
|
||||
│ Layer 3: Metadata Protection (Separate AES-GCM) │
|
||||
│ Layer 2: Key Exchange (ECDH P-384) │
|
||||
│ Layer 1: Enhanced Authentication (ECDSA P-384) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ TRANSPORT LAYER │
|
||||
│ (WebRTC/ICE/DTLS) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```text
|
||||
connection established
|
||||
↓
|
||||
shared keys derived
|
||||
↓
|
||||
deterministic SAS displayed
|
||||
↓
|
||||
manual out-of-band comparison
|
||||
↓
|
||||
local input validated
|
||||
↓
|
||||
peer confirmation received
|
||||
↓
|
||||
verified session
|
||||
```
|
||||
|
||||
### Security Progression Stages
|
||||
The verified state is reached only when both local and remote confirmation flags are true.
|
||||
|
||||
| Stage | Layers Active | Security Level | Target Threats |
|
||||
|-------|---------------|----------------|-----------------|
|
||||
| 1 | 1-5 | Basic Enhanced | Basic attacks, MITM |
|
||||
| 2 | 1-7 | Medium | + Traffic analysis |
|
||||
| 3 | 1-9 | High | + Timing attacks |
|
||||
| 4 | 1-12 | Maximum | + Advanced persistent threats |
|
||||
## File-transfer architecture
|
||||
|
||||
---
|
||||
1. sender emits metadata
|
||||
2. receiver validates name, size, type, and abuse limits
|
||||
3. receiver sees Accept / Reject prompt
|
||||
4. no receive buffers are allocated before acceptance
|
||||
5. sender transmits chunks only after acceptance
|
||||
6. completed received buffers are retained within a bounded window
|
||||
|
||||
## 🔍 Layer-by-Layer Analysis
|
||||
## Disconnect cleanup
|
||||
|
||||
### Layer 1: Enhanced Authentication (ECDSA P-384)
|
||||
**Purpose:** Cryptographic proof of message authenticity and sender verification
|
||||
The canonical disconnect path clears:
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Algorithm:** ECDSA with P-384 curve
|
||||
- **Hash Function:** SHA-384 (primary), SHA-256 (fallback)
|
||||
- **Key Size:** 384-bit (equivalent to 7680-bit RSA)
|
||||
- **Signature Size:** 96 bytes
|
||||
- **Key Properties:** Non-extractable, hardware-protected
|
||||
|
||||
**Implementation:**
|
||||
```javascript
|
||||
// Self-signed key package for MITM protection
|
||||
const keyPackage = {
|
||||
keyType: 'ECDSA',
|
||||
keyData: exported384BitKey,
|
||||
timestamp: Date.now(),
|
||||
version: '4.0',
|
||||
signature: ecdsaSignature
|
||||
};
|
||||
```
|
||||
|
||||
**Protection Against:**
|
||||
- Message tampering
|
||||
- Sender impersonation
|
||||
- Man-in-the-middle attacks
|
||||
- Key substitution attacks
|
||||
|
||||
---
|
||||
|
||||
### Layer 2: Key Exchange (ECDH P-384)
|
||||
**Purpose:** Secure key agreement between peers without central authority
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Algorithm:** Elliptic Curve Diffie-Hellman
|
||||
- **Curve:** NIST P-384 (secp384r1)
|
||||
- **Key Derivation:** HKDF with SHA-384
|
||||
- **Salt Size:** 64 bytes (enhanced from standard 32 bytes)
|
||||
- **Context Info:** "SecureBit.chat v4.0 Enhanced Security Edition"
|
||||
|
||||
**Key Derivation Process:**
|
||||
```javascript
|
||||
// Triple key derivation for maximum security
|
||||
const derivedKeys = {
|
||||
encryptionKey: HKDF(sharedSecret, salt, "message-encryption-v4"),
|
||||
macKey: HKDF(sharedSecret, salt, "message-authentication-v4"),
|
||||
metadataKey: HKDF(sharedSecret, salt, "metadata-protection-v4")
|
||||
};
|
||||
```
|
||||
|
||||
**Protection Against:**
|
||||
- Passive eavesdropping
|
||||
- Key recovery attacks
|
||||
- Weak key generation
|
||||
- Quantum computer threats (post-quantum resistant)
|
||||
|
||||
---
|
||||
|
||||
### Layer 3: Metadata Protection (Separate AES-GCM)
|
||||
**Purpose:** Protect message metadata from analysis and correlation
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Algorithm:** AES-256-GCM
|
||||
- **Key:** Separate 256-bit key derived from ECDH
|
||||
- **IV:** 96-bit random per message
|
||||
- **Authentication:** Integrated GMAC
|
||||
- **Protected Data:** Message ID, timestamp, sequence number, original length
|
||||
|
||||
**Metadata Structure:**
|
||||
```javascript
|
||||
const protectedMetadata = {
|
||||
id: "msg_timestamp_counter",
|
||||
timestamp: encryptedTimestamp,
|
||||
sequenceNumber: encryptedSequence,
|
||||
originalLength: encryptedLength,
|
||||
version: "4.0"
|
||||
};
|
||||
```
|
||||
|
||||
**Protection Against:**
|
||||
- Traffic flow analysis
|
||||
- Message correlation attacks
|
||||
- Timing analysis
|
||||
- Size-based fingerprinting
|
||||
|
||||
---
|
||||
|
||||
### Layer 4: Message Encryption (Enhanced AES-GCM)
|
||||
**Purpose:** Primary message content protection with authenticated encryption
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Algorithm:** AES-256-GCM
|
||||
- **Key:** 256-bit derived from ECDH
|
||||
- **IV:** 96-bit random per message
|
||||
- **Authentication:** Integrated GMAC + separate HMAC
|
||||
- **Padding:** PKCS#7 + random padding
|
||||
- **MAC Algorithm:** HMAC-SHA-384
|
||||
|
||||
**Enhanced Features:**
|
||||
- Sequence number validation
|
||||
- Replay attack prevention
|
||||
- Message integrity verification
|
||||
- Deterministic serialization for MAC
|
||||
|
||||
**Protection Against:**
|
||||
- Content interception
|
||||
- Message modification
|
||||
- Replay attacks
|
||||
- Authentication bypass
|
||||
|
||||
---
|
||||
|
||||
### Layer 5: Nested Encryption (Additional AES-GCM)
|
||||
**Purpose:** Second layer of encryption for maximum confidentiality
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Algorithm:** AES-256-GCM (independent instance)
|
||||
- **Key:** Separate 256-bit key (hardware-generated)
|
||||
- **IV:** 96-bit unique per encryption
|
||||
- **Counter:** Incremental counter for IV uniqueness
|
||||
- **Key Rotation:** Every 1000 messages or 15 minutes
|
||||
|
||||
**Implementation:**
|
||||
```javascript
|
||||
// Nested encryption with unique IV
|
||||
const uniqueIV = new Uint8Array(12);
|
||||
uniqueIV.set(baseIV);
|
||||
uniqueIV[11] = (counter++) & 0xFF;
|
||||
|
||||
const nestedEncrypted = await crypto.subtle.encrypt(
|
||||
{ name: 'AES-GCM', iv: uniqueIV },
|
||||
nestedEncryptionKey,
|
||||
alreadyEncryptedData
|
||||
);
|
||||
```
|
||||
|
||||
**Protection Against:**
|
||||
- Cryptographic implementation flaws
|
||||
- Algorithm-specific attacks
|
||||
- Side-channel attacks
|
||||
- Future cryptographic breaks
|
||||
|
||||
---
|
||||
|
||||
### Layer 6: Packet Padding (Size Obfuscation)
|
||||
**Purpose:** Hide real message sizes to prevent traffic analysis
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Padding Range:** 64-1024 bytes (configurable)
|
||||
- **Algorithm:** Cryptographically secure random
|
||||
- **Distribution:** Uniform random within range
|
||||
- **Header:** 4-byte original size indicator
|
||||
- **Efficiency:** Optimized for minimal overhead
|
||||
|
||||
**Padding Algorithm:**
|
||||
```javascript
|
||||
const paddingSize = Math.floor(Math.random() *
|
||||
(maxPadding - minPadding + 1)) + minPadding;
|
||||
const padding = crypto.getRandomValues(new Uint8Array(paddingSize));
|
||||
|
||||
// Structure: [originalSize:4][originalData][randomPadding]
|
||||
```
|
||||
|
||||
**Protection Against:**
|
||||
- Message size analysis
|
||||
- Traffic pattern recognition
|
||||
- Statistical correlation attacks
|
||||
- Content-based fingerprinting
|
||||
|
||||
---
|
||||
|
||||
### Layer 7: Anti-Fingerprinting (Pattern Obfuscation)
|
||||
**Purpose:** Prevent behavioral analysis and traffic fingerprinting
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Noise Injection:** 8-40 bytes random data
|
||||
- **Size Randomization:** ±25% size variation
|
||||
- **Pattern Masking:** XOR with cryptographic noise
|
||||
- **Header Randomization:** Fake headers injection
|
||||
- **Timing Obfuscation:** Random delays (50-1000ms)
|
||||
|
||||
**Obfuscation Techniques:**
|
||||
```javascript
|
||||
// Multi-layer obfuscation
|
||||
const obfuscated = {
|
||||
addNoise: () => injectRandomBytes(8, 40),
|
||||
randomizeSize: () => varySize(0.75, 1.25),
|
||||
maskPatterns: () => xorWithNoise(data),
|
||||
addFakeHeaders: () => injectFakeHeaders(1, 3)
|
||||
};
|
||||
```
|
||||
|
||||
**Protection Against:**
|
||||
- Behavioral fingerprinting
|
||||
- Machine learning classification
|
||||
- Protocol identification
|
||||
- Application detection
|
||||
|
||||
---
|
||||
|
||||
### Layer 8: Packet Reordering Protection (Sequence Security)
|
||||
**Purpose:** Maintain message integrity despite network reordering
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Sequence Numbers:** 32-bit incremental
|
||||
- **Timestamps:** 32-bit Unix timestamp
|
||||
- **Buffer Size:** Maximum 10 out-of-order packets
|
||||
- **Timeout:** 5 seconds for reordering
|
||||
- **Header Size:** 8-12 bytes (depending on configuration)
|
||||
|
||||
**Reordering Algorithm:**
|
||||
```javascript
|
||||
// Packet structure: [sequence:4][timestamp:4][size:4][data]
|
||||
const packetHeader = {
|
||||
sequence: sequenceNumber++,
|
||||
timestamp: Date.now(),
|
||||
dataSize: actualDataLength
|
||||
};
|
||||
```
|
||||
|
||||
**Protection Against:**
|
||||
- Packet injection attacks
|
||||
- Sequence number attacks
|
||||
- Network-level tampering
|
||||
- Order-dependent vulnerabilities
|
||||
|
||||
---
|
||||
|
||||
### Layer 9: Message Chunking (Timing Analysis Protection)
|
||||
**Purpose:** Break large messages into randomized chunks with delays
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Chunk Size:** Maximum 1024-2048 bytes
|
||||
- **Delay Range:** 50-300ms between chunks
|
||||
- **Randomization:** True randomness for delays and sizes
|
||||
- **Headers:** 16-byte chunk identification
|
||||
- **Reassembly:** Timeout-based with 5-second limit
|
||||
|
||||
**Chunking Structure:**
|
||||
```javascript
|
||||
// Chunk header: [messageId:4][chunkIndex:4][totalChunks:4][chunkSize:4]
|
||||
const chunkHeader = {
|
||||
messageId: uniqueMessageId,
|
||||
chunkIndex: currentChunk,
|
||||
totalChunks: totalChunkCount,
|
||||
chunkSize: thisChunkSize
|
||||
};
|
||||
```
|
||||
|
||||
**Protection Against:**
|
||||
- Timing correlation attacks
|
||||
- Large message identification
|
||||
- Burst analysis
|
||||
- Real-time content analysis
|
||||
|
||||
---
|
||||
|
||||
### Layer 10: Fake Traffic Generation (Traffic Analysis Protection)
|
||||
**Purpose:** Generate convincing decoy traffic to mask real communications
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Frequency:** 10-30 second intervals
|
||||
- **Size Range:** 32-256 bytes
|
||||
- **Patterns:** 5 different message types
|
||||
- **Encryption:** Full security layer processing
|
||||
- **Detection:** Invisible to users (filtered at receiver)
|
||||
|
||||
**Fake Message Types:**
|
||||
```javascript
|
||||
const fakePatterns = {
|
||||
'heartbeat': () => generateHeartbeatPattern(),
|
||||
'status': () => generateStatusPattern(),
|
||||
'sync': () => generateSyncPattern(),
|
||||
'ping': () => generatePingPattern(),
|
||||
'pong': () => generatePongPattern()
|
||||
};
|
||||
```
|
||||
|
||||
**Protection Against:**
|
||||
- Traffic volume analysis
|
||||
- Communication timing analysis
|
||||
- Silence period detection
|
||||
- Conversation pattern recognition
|
||||
|
||||
---
|
||||
|
||||
### Layer 11: Enhanced Rate Limiting (DDoS Protection)
|
||||
**Purpose:** Prevent abuse and ensure service availability
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Message Rate:** 60 messages per minute
|
||||
- **Connection Rate:** 5 connections per 5 minutes
|
||||
- **Sliding Window:** Time-based with cleanup
|
||||
- **Verification:** Cryptographic rate tokens
|
||||
- **Storage:** In-memory with automatic cleanup
|
||||
|
||||
**Rate Limiting Algorithm:**
|
||||
```javascript
|
||||
const rateLimits = {
|
||||
messages: new Map(), // identifier -> timestamps[]
|
||||
connections: new Map(), // identifier -> timestamps[]
|
||||
cleanup: () => removeExpiredEntries(1, 'hour')
|
||||
};
|
||||
```
|
||||
|
||||
**Protection Against:**
|
||||
- Message flooding attacks
|
||||
- Connection exhaustion
|
||||
- Resource consumption attacks
|
||||
- Service degradation
|
||||
|
||||
---
|
||||
|
||||
### Layer 12: Perfect Forward Secrecy (Key Rotation)
|
||||
**Purpose:** Ensure past communications remain secure even if keys are compromised
|
||||
|
||||
**Technical Specifications:**
|
||||
- **Rotation Interval:** 5 minutes or 100 messages
|
||||
- **Key Versions:** Tracked with version numbers
|
||||
- **Old Key Storage:** Maximum 3 previous versions (15 minutes)
|
||||
- **Rotation Protocol:** Automated with peer coordination
|
||||
- **Cleanup:** Automatic old key destruction
|
||||
|
||||
**Key Rotation Process:**
|
||||
```javascript
|
||||
const pfsImplementation = {
|
||||
rotationTrigger: () => checkTime(5, 'minutes') || checkMessages(100),
|
||||
keyVersioning: () => incrementVersion(),
|
||||
oldKeyCleanup: () => removeKeysOlderThan(15, 'minutes'),
|
||||
automaticRotation: () => rotateIfNeeded()
|
||||
};
|
||||
```
|
||||
|
||||
**Protection Against:**
|
||||
- Long-term key compromise
|
||||
- Historical data decryption
|
||||
- Persistent surveillance
|
||||
- Future cryptographic breaks
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Cryptographic Specifications
|
||||
|
||||
### Algorithm Selection Rationale
|
||||
|
||||
| Component | Algorithm | Key Size | Rationale |
|
||||
|-----------|-----------|----------|-----------|
|
||||
| Key Exchange | ECDH P-384 | 384-bit | NSA Suite B, quantum-resistant timeline |
|
||||
| Signatures | ECDSA P-384 | 384-bit | Matches key exchange, proven security |
|
||||
| Encryption | AES-256-GCM | 256-bit | NIST recommended, authenticated encryption |
|
||||
| Hashing | SHA-384 | 384-bit | Matches curve size, collision resistant |
|
||||
| MAC | HMAC-SHA-384 | 384-bit | Proven security, matches hash function |
|
||||
|
||||
### Security Strengths
|
||||
|
||||
- **ECDH P-384:** Equivalent to 7680-bit RSA
|
||||
- **AES-256:** Quantum computer resistant until 2040+
|
||||
- **SHA-384:** 192-bit security level (collision resistance)
|
||||
- **Combined Security:** Exceeds 256-bit security level
|
||||
|
||||
### Cryptographic Operations Performance
|
||||
|
||||
| Operation | Time (ms) | CPU Usage | Memory Usage |
|
||||
|-----------|-----------|-----------|--------------|
|
||||
| Key Generation | 10-50 | Medium | Low |
|
||||
| ECDH Agreement | 5-15 | Low | Low |
|
||||
| AES Encryption | 0.1-1 | Very Low | Very Low |
|
||||
| ECDSA Signing | 2-8 | Low | Low |
|
||||
| Message Processing | 1-5 | Low | Low |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Threat Model
|
||||
|
||||
### Threat Classifications
|
||||
|
||||
#### **🔴 Critical Threats (Fully Mitigated)**
|
||||
- **Nation-State Attacks:** Advanced persistent threats
|
||||
- **MITM Attacks:** Certificate pinning bypass attempts
|
||||
- **Cryptographic Attacks:** Implementation vulnerabilities
|
||||
- **Traffic Analysis:** Deep packet inspection and metadata analysis
|
||||
|
||||
#### **🟡 High Threats (Substantially Mitigated)**
|
||||
- **Side-Channel Attacks:** Timing and power analysis
|
||||
- **Social Engineering:** User manipulation (partially mitigated)
|
||||
- **Endpoint Compromise:** Device-level attacks
|
||||
- **Quantum Computing:** Future quantum attacks (timeline > 15 years)
|
||||
|
||||
#### **🟢 Medium Threats (Completely Mitigated)**
|
||||
- **Passive Eavesdropping:** Network traffic interception
|
||||
- **Replay Attacks:** Message reuse attempts
|
||||
- **DDoS Attacks:** Service disruption attempts
|
||||
- **Protocol Downgrade:** Forced weak encryption
|
||||
|
||||
### Attack Scenarios and Defenses
|
||||
|
||||
#### Scenario 1: Government Surveillance
|
||||
**Attack:** Comprehensive traffic monitoring and analysis
|
||||
**Defense Layers:** 6, 7, 10, 12 (traffic obfuscation)
|
||||
**Result:** Encrypted traffic indistinguishable from noise
|
||||
|
||||
#### Scenario 2: Corporate Espionage
|
||||
**Attack:** Targeted interception with advanced tools
|
||||
**Defense Layers:** 1, 2, 3, 4, 5 (cryptographic protection)
|
||||
**Result:** Computationally infeasible to decrypt
|
||||
|
||||
#### Scenario 3: ISP-Level Monitoring
|
||||
**Attack:** Deep packet inspection and metadata collection
|
||||
**Defense Layers:** 6, 7, 8, 9, 10 (pattern obfuscation)
|
||||
**Result:** No useful metadata or patterns extractable
|
||||
|
||||
#### Scenario 4: Academic Cryptanalysis
|
||||
**Attack:** Advanced mathematical attacks on crypto
|
||||
**Defense Layers:** 2, 4, 5 (multiple algorithms)
|
||||
**Result:** Multiple independent cryptographic barriers
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Implementation Details
|
||||
|
||||
### Message Flow Through Security Layers
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ User Message │
|
||||
└─────────┬───────┘
|
||||
│
|
||||
┌─────────▼───────┐ ┌──────────────────┐
|
||||
│ Layer 7: Anti │ │ Outbound Process │
|
||||
│ Fingerprinting │◄───┤ (Sending) │
|
||||
└─────────┬───────┘ └──────────────────┘
|
||||
│
|
||||
┌─────────▼───────┐
|
||||
│ Layer 6: Packet│
|
||||
│ Padding │
|
||||
└─────────┬───────┘
|
||||
│
|
||||
┌─────────▼───────┐
|
||||
│ Layer 8: Packet│
|
||||
│ Reordering │
|
||||
└─────────┬───────┘
|
||||
│
|
||||
┌─────────▼───────┐
|
||||
│ Layer 5: Nested│
|
||||
│ Encryption │
|
||||
└─────────┬───────┘
|
||||
│
|
||||
┌─────────▼───────┐
|
||||
│ Layer 4: Message│
|
||||
│ Encryption │
|
||||
└─────────┬───────┘
|
||||
│
|
||||
┌─────────▼───────┐
|
||||
│ WebRTC Channel │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### Security Layer Configuration
|
||||
|
||||
```javascript
|
||||
// Production configuration for maximum security
|
||||
const securityConfig = {
|
||||
// Stage 4 - Maximum Security
|
||||
features: {
|
||||
hasEncryption: true,
|
||||
hasECDH: true,
|
||||
hasECDSA: true,
|
||||
hasMutualAuth: true,
|
||||
hasMetadataProtection: true,
|
||||
hasEnhancedReplayProtection: true,
|
||||
hasNonExtractableKeys: true,
|
||||
hasRateLimiting: true,
|
||||
hasEnhancedValidation: true,
|
||||
hasPFS: true,
|
||||
hasNestedEncryption: true,
|
||||
hasPacketPadding: true,
|
||||
hasFakeTraffic: true,
|
||||
hasMessageChunking: true,
|
||||
hasDecoyChannels: true,
|
||||
hasPacketReordering: true,
|
||||
hasAntiFingerprinting: true
|
||||
},
|
||||
|
||||
// Performance optimizations
|
||||
performance: {
|
||||
paddingRange: [64, 512], // Reduced for efficiency
|
||||
chunkSize: 2048, // Larger chunks
|
||||
fakeTrafficInterval: [10000, 30000], // Less frequent
|
||||
keyRotation: 300000 // 5 minutes
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Security Verification
|
||||
|
||||
### Automated Testing
|
||||
|
||||
```javascript
|
||||
// Security layer verification
|
||||
async function verifySecurityLayers() {
|
||||
const tests = [
|
||||
verifyEncryption(),
|
||||
verifyECDHKeyExchange(),
|
||||
verifyECDSASignatures(),
|
||||
verifyMutualAuth(),
|
||||
verifyMetadataProtection(),
|
||||
verifyReplayProtection(),
|
||||
verifyNonExtractableKeys(),
|
||||
verifyRateLimiting(),
|
||||
verifyEnhancedValidation(),
|
||||
verifyPFS(),
|
||||
verifyNestedEncryption(),
|
||||
verifyPacketPadding()
|
||||
];
|
||||
|
||||
const results = await Promise.all(tests);
|
||||
return results.every(result => result === true);
|
||||
}
|
||||
```
|
||||
|
||||
### Manual Verification Commands
|
||||
|
||||
```javascript
|
||||
// Check security status
|
||||
webrtcManager.getSecurityStatus()
|
||||
// Expected: { stage: 4, securityLevel: 'MAXIMUM', activeFeatures: 12 }
|
||||
|
||||
// Verify cryptographic implementation
|
||||
webrtcManager.calculateSecurityLevel()
|
||||
// Expected: { level: 'HIGH', score: 80+, verificationResults: {...} }
|
||||
|
||||
// Test fake traffic filtering
|
||||
webrtcManager.checkFakeTrafficStatus()
|
||||
// Expected: { fakeTrafficEnabled: true, timerActive: true }
|
||||
```
|
||||
|
||||
### Security Metrics Dashboard
|
||||
|
||||
| Metric | Target | Current | Status |
|
||||
|--------|---------|---------|---------|
|
||||
| Active Security Layers | 12 | 12 | ✅ |
|
||||
| Encryption Strength | 256-bit | 256-bit | ✅ |
|
||||
| Key Exchange Security | P-384 | P-384 | ✅ |
|
||||
| Forward Secrecy | Complete | Complete | ✅ |
|
||||
| Traffic Obfuscation | Maximum | Maximum | ✅ |
|
||||
| Attack Surface | Minimal | Minimal | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Performance Impact
|
||||
|
||||
### Latency Analysis
|
||||
|
||||
| Security Layer | Added Latency | Justification |
|
||||
|----------------|---------------|---------------|
|
||||
| Authentication | ~5ms | Necessary for MITM protection |
|
||||
| Key Exchange | ~10ms | One-time cost per session |
|
||||
| Metadata Protection | ~1ms | Minimal overhead |
|
||||
| Message Encryption | ~2ms | Standard AES-GCM performance |
|
||||
| Nested Encryption | ~2ms | Additional security layer |
|
||||
| Packet Padding | ~0.5ms | Simple padding operation |
|
||||
| Anti-Fingerprinting | ~3ms | Pattern obfuscation |
|
||||
| Reordering Protection | ~1ms | Header processing |
|
||||
| Message Chunking | ~50ms | Intentional delay for security |
|
||||
| Fake Traffic | 0ms | Background operation |
|
||||
| Rate Limiting | ~0.1ms | Memory lookup |
|
||||
| PFS Key Rotation | ~10ms | Every 5 minutes |
|
||||
|
||||
**Total Average Latency:** ~75ms per message (acceptable for secure communications)
|
||||
|
||||
### Throughput Impact
|
||||
|
||||
- **Without Security:** ~1000 messages/second
|
||||
- **With Stage 4 Security:** ~500 messages/second
|
||||
- **Efficiency:** 50% (excellent for security level provided)
|
||||
|
||||
### Memory Usage
|
||||
|
||||
- **Base Memory:** ~2MB
|
||||
- **Security Layers:** ~3MB additional
|
||||
- **Cryptographic Keys:** ~1MB
|
||||
- **Total:** ~6MB (minimal for modern devices)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Compliance Standards
|
||||
|
||||
### Industry Standards Met
|
||||
|
||||
- ✅ **NIST SP 800-57:** Key management best practices
|
||||
- ✅ **FIPS 140-2 Level 2:** Cryptographic module security
|
||||
- ✅ **NSA Suite B:** Cryptographic algorithms (P-384, AES-256)
|
||||
- ✅ **RFC 7748:** Elliptic curve cryptography standards
|
||||
- ✅ **RFC 5869:** HKDF key derivation
|
||||
- ✅ **RFC 3394:** AES key wrap specifications
|
||||
|
||||
### Regulatory Compliance
|
||||
|
||||
- ✅ **GDPR:** Privacy by design, data minimization
|
||||
- ✅ **CCPA:** California privacy protection
|
||||
- ✅ **HIPAA:** Healthcare data protection (suitable for)
|
||||
- ✅ **SOX:** Financial data protection (suitable for)
|
||||
- ✅ **ITAR:** International traffic in arms regulations (crypto export)
|
||||
|
||||
### Security Certifications (Pending)
|
||||
|
||||
- 🔄 **Common Criteria EAL4+:** Security functionality evaluation
|
||||
- 🔄 **FIPS 140-3:** Next-generation cryptographic validation
|
||||
- 🔄 **ISO 27001:** Information security management
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Future Enhancements
|
||||
|
||||
### Quantum-Resistant Cryptography (2026)
|
||||
- **Post-Quantum Key Exchange:** CRYSTALS-Kyber
|
||||
- **Post-Quantum Signatures:** CRYSTALS-Dilithium
|
||||
- **Hybrid Classical/Quantum:** Dual algorithm support
|
||||
|
||||
### Advanced Traffic Obfuscation (2025)
|
||||
- **AI-Powered Pattern Generation:** Machine learning fake traffic
|
||||
- **Protocol Mimicry:** Disguise as common protocols (HTTP, DNS)
|
||||
- **Adaptive Obfuscation:** Real-time pattern adjustment
|
||||
|
||||
### Enhanced Perfect Forward Secrecy (2025)
|
||||
- **Automatic Key Rotation:** Every 1 minute
|
||||
- **Group Key Management:** Multi-party key rotation
|
||||
- **Quantum Key Distribution:** Hardware-based key generation
|
||||
|
||||
---
|
||||
|
||||
## 📞 Technical Support
|
||||
|
||||
For technical questions about the security architecture:
|
||||
|
||||
- **Security Team:** security@SecureBit.chat
|
||||
- **Technical Documentation:** docs@SecureBit.chat
|
||||
- **GitHub Issues:** [Security Architecture Issues](https://github.com/SecureBitChat/securebit-chat/issues?q=label%3Asecurity-architecture)
|
||||
|
||||
---
|
||||
|
||||
*This document is updated with each major security enhancement. Current version reflects Stage 4 Maximum Security implementation.*
|
||||
|
||||
**Last Updated:** January 14, 2025
|
||||
**Document Version:** 4.0
|
||||
**Security Implementation:** Stage 4 - Maximum Security
|
||||
**Review Status:** ✅ Verified and Tested
|
||||
- WebRTC channels and peer connection handles
|
||||
- timers, deferred retries, fake traffic, and decoy traffic
|
||||
- pending transfer state and consent waits
|
||||
- verification state and crypto/PFS state
|
||||
- React file-transfer callbacks and stale UI transfer state
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
(function(){/*
|
||||
Modernizr 3.0.0pre (Custom Build) | MIT
|
||||
*/
|
||||
'use strict';(function(Q,zb){"object"===typeof exports&&"undefined"!==typeof module?zb(exports,require("react")):"function"===typeof define&&define.amd?define(["exports","react"],zb):(Q=Q||self,zb(Q.ReactDOM={},Q.React))})(this,function(Q,zb){function m(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;c<arguments.length;c++)b+="&args[]="+encodeURIComponent(arguments[c]);return"Minified React error #"+a+"; visit "+b+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}
|
||||
function mb(a,b){Ab(a,b);Ab(a+"Capture",b)}function Ab(a,b){$b[a]=b;for(a=0;a<b.length;a++)cg.add(b[a])}function bj(a){if(Zd.call(dg,a))return!0;if(Zd.call(eg,a))return!1;if(cj.test(a))return dg[a]=!0;eg[a]=!0;return!1}function dj(a,b,c,d){if(null!==c&&0===c.type)return!1;switch(typeof b){case "function":case "symbol":return!0;case "boolean":if(d)return!1;if(null!==c)return!c.acceptsBooleans;a=a.toLowerCase().slice(0,5);return"data-"!==a&&"aria-"!==a;default:return!1}}function ej(a,b,c,d){if(null===
|
||||
b||"undefined"===typeof b||dj(a,b,c,d))return!0;if(d)return!1;if(null!==c)switch(c.type){case 3:return!b;case 4:return!1===b;case 5:return isNaN(b);case 6:return isNaN(b)||1>b}return!1}function Y(a,b,c,d,e,f,g){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f;this.removeEmptyString=g}function $d(a,b,c,d){var e=R.hasOwnProperty(b)?R[b]:null;if(null!==e?0!==e.type:d||!(2<b.length)||"o"!==
|
||||
b[0]&&"O"!==b[0]||"n"!==b[1]&&"N"!==b[1])ej(b,c,e,d)&&(c=null),d||null===e?bj(b)&&(null===c?a.removeAttribute(b):a.setAttribute(b,""+c)):e.mustUseProperty?a[e.propertyName]=null===c?3===e.type?!1:"":c:(b=e.attributeName,d=e.attributeNamespace,null===c?a.removeAttribute(b):(e=e.type,c=3===e||4===e&&!0===c?"":""+c,d?a.setAttributeNS(d,b,c):a.setAttribute(b,c)))}function ac(a){if(null===a||"object"!==typeof a)return null;a=fg&&a[fg]||a["@@iterator"];return"function"===typeof a?a:null}function bc(a,b,
|
||||
c){if(void 0===ae)try{throw Error();}catch(d){ae=(b=d.stack.trim().match(/\n( *(at )?)/))&&b[1]||""}return"\n"+ae+a}function be(a,b){if(!a||ce)return"";ce=!0;var c=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(b)if(b=function(){throw Error();},Object.defineProperty(b.prototype,"props",{set:function(){throw Error();}}),"object"===typeof Reflect&&Reflect.construct){try{Reflect.construct(b,[])}catch(n){var d=n}Reflect.construct(a,[],b)}else{try{b.call()}catch(n){d=n}a.call(b.prototype)}else{try{throw Error();
|
||||
}catch(n){d=n}a()}}catch(n){if(n&&d&&"string"===typeof n.stack){for(var e=n.stack.split("\n"),f=d.stack.split("\n"),g=e.length-1,h=f.length-1;1<=g&&0<=h&&e[g]!==f[h];)h--;for(;1<=g&&0<=h;g--,h--)if(e[g]!==f[h]){if(1!==g||1!==h){do if(g--,h--,0>h||e[g]!==f[h]){var k="\n"+e[g].replace(" at new "," at ");a.displayName&&k.includes("<anonymous>")&&(k=k.replace("<anonymous>",a.displayName));return k}while(1<=g&&0<=h)}break}}}finally{ce=!1,Error.prepareStackTrace=c}return(a=a?a.displayName||a.name:"")?bc(a):
|
||||
""}function fj(a){switch(a.tag){case 5:return bc(a.type);case 16:return bc("Lazy");case 13:return bc("Suspense");case 19:return bc("SuspenseList");case 0:case 2:case 15:return a=be(a.type,!1),a;case 11:return a=be(a.type.render,!1),a;case 1:return a=be(a.type,!0),a;default:return""}}function de(a){if(null==a)return null;if("function"===typeof a)return a.displayName||a.name||null;if("string"===typeof a)return a;switch(a){case Bb:return"Fragment";case Cb:return"Portal";case ee:return"Profiler";case fe:return"StrictMode";
|
||||
case ge:return"Suspense";case he:return"SuspenseList"}if("object"===typeof a)switch(a.$$typeof){case gg:return(a.displayName||"Context")+".Consumer";case hg:return(a._context.displayName||"Context")+".Provider";case ie:var b=a.render;a=a.displayName;a||(a=b.displayName||b.name||"",a=""!==a?"ForwardRef("+a+")":"ForwardRef");return a;case je:return b=a.displayName||null,null!==b?b:de(a.type)||"Memo";case Ta:b=a._payload;a=a._init;try{return de(a(b))}catch(c){}}return null}function gj(a){var b=a.type;
|
||||
switch(a.tag){case 24:return"Cache";case 9:return(b.displayName||"Context")+".Consumer";case 10:return(b._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return a=b.render,a=a.displayName||a.name||"",b.displayName||(""!==a?"ForwardRef("+a+")":"ForwardRef");case 7:return"Fragment";case 5:return b;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return de(b);case 8:return b===fe?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";
|
||||
case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if("function"===typeof b)return b.displayName||b.name||null;if("string"===typeof b)return b}return null}function Ua(a){switch(typeof a){case "boolean":case "number":case "string":case "undefined":return a;case "object":return a;default:return""}}function ig(a){var b=a.type;return(a=a.nodeName)&&"input"===a.toLowerCase()&&("checkbox"===b||"radio"===
|
||||
b)}function hj(a){var b=ig(a)?"checked":"value",c=Object.getOwnPropertyDescriptor(a.constructor.prototype,b),d=""+a[b];if(!a.hasOwnProperty(b)&&"undefined"!==typeof c&&"function"===typeof c.get&&"function"===typeof c.set){var e=c.get,f=c.set;Object.defineProperty(a,b,{configurable:!0,get:function(){return e.call(this)},set:function(a){d=""+a;f.call(this,a)}});Object.defineProperty(a,b,{enumerable:c.enumerable});return{getValue:function(){return d},setValue:function(a){d=""+a},stopTracking:function(){a._valueTracker=
|
||||
null;delete a[b]}}}}function Pc(a){a._valueTracker||(a._valueTracker=hj(a))}function jg(a){if(!a)return!1;var b=a._valueTracker;if(!b)return!0;var c=b.getValue();var d="";a&&(d=ig(a)?a.checked?"true":"false":a.value);a=d;return a!==c?(b.setValue(a),!0):!1}function Qc(a){a=a||("undefined"!==typeof document?document:void 0);if("undefined"===typeof a)return null;try{return a.activeElement||a.body}catch(b){return a.body}}function ke(a,b){var c=b.checked;return E({},b,{defaultChecked:void 0,defaultValue:void 0,
|
||||
value:void 0,checked:null!=c?c:a._wrapperState.initialChecked})}function kg(a,b){var c=null==b.defaultValue?"":b.defaultValue,d=null!=b.checked?b.checked:b.defaultChecked;c=Ua(null!=b.value?b.value:c);a._wrapperState={initialChecked:d,initialValue:c,controlled:"checkbox"===b.type||"radio"===b.type?null!=b.checked:null!=b.value}}function lg(a,b){b=b.checked;null!=b&&$d(a,"checked",b,!1)}function le(a,b){lg(a,b);var c=Ua(b.value),d=b.type;if(null!=c)if("number"===d){if(0===c&&""===a.value||a.value!=
|
||||
c)a.value=""+c}else a.value!==""+c&&(a.value=""+c);else if("submit"===d||"reset"===d){a.removeAttribute("value");return}b.hasOwnProperty("value")?me(a,b.type,c):b.hasOwnProperty("defaultValue")&&me(a,b.type,Ua(b.defaultValue));null==b.checked&&null!=b.defaultChecked&&(a.defaultChecked=!!b.defaultChecked)}function mg(a,b,c){if(b.hasOwnProperty("value")||b.hasOwnProperty("defaultValue")){var d=b.type;if(!("submit"!==d&&"reset"!==d||void 0!==b.value&&null!==b.value))return;b=""+a._wrapperState.initialValue;
|
||||
c||b===a.value||(a.value=b);a.defaultValue=b}c=a.name;""!==c&&(a.name="");a.defaultChecked=!!a._wrapperState.initialChecked;""!==c&&(a.name=c)}function me(a,b,c){if("number"!==b||Qc(a.ownerDocument)!==a)null==c?a.defaultValue=""+a._wrapperState.initialValue:a.defaultValue!==""+c&&(a.defaultValue=""+c)}function Db(a,b,c,d){a=a.options;if(b){b={};for(var e=0;e<c.length;e++)b["$"+c[e]]=!0;for(c=0;c<a.length;c++)e=b.hasOwnProperty("$"+a[c].value),a[c].selected!==e&&(a[c].selected=e),e&&d&&(a[c].defaultSelected=
|
||||
!0)}else{c=""+Ua(c);b=null;for(e=0;e<a.length;e++){if(a[e].value===c){a[e].selected=!0;d&&(a[e].defaultSelected=!0);return}null!==b||a[e].disabled||(b=a[e])}null!==b&&(b.selected=!0)}}function ne(a,b){if(null!=b.dangerouslySetInnerHTML)throw Error(m(91));return E({},b,{value:void 0,defaultValue:void 0,children:""+a._wrapperState.initialValue})}function ng(a,b){var c=b.value;if(null==c){c=b.children;b=b.defaultValue;if(null!=c){if(null!=b)throw Error(m(92));if(cc(c)){if(1<c.length)throw Error(m(93));
|
||||
c=c[0]}b=c}null==b&&(b="");c=b}a._wrapperState={initialValue:Ua(c)}}function og(a,b){var c=Ua(b.value),d=Ua(b.defaultValue);null!=c&&(c=""+c,c!==a.value&&(a.value=c),null==b.defaultValue&&a.defaultValue!==c&&(a.defaultValue=c));null!=d&&(a.defaultValue=""+d)}function pg(a,b){b=a.textContent;b===a._wrapperState.initialValue&&""!==b&&null!==b&&(a.value=b)}function qg(a){switch(a){case "svg":return"http://www.w3.org/2000/svg";case "math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}
|
||||
function oe(a,b){return null==a||"http://www.w3.org/1999/xhtml"===a?qg(b):"http://www.w3.org/2000/svg"===a&&"foreignObject"===b?"http://www.w3.org/1999/xhtml":a}function rg(a,b,c){return null==b||"boolean"===typeof b||""===b?"":c||"number"!==typeof b||0===b||dc.hasOwnProperty(a)&&dc[a]?(""+b).trim():b+"px"}function sg(a,b){a=a.style;for(var c in b)if(b.hasOwnProperty(c)){var d=0===c.indexOf("--"),e=rg(c,b[c],d);"float"===c&&(c="cssFloat");d?a.setProperty(c,e):a[c]=e}}function pe(a,b){if(b){if(ij[a]&&
|
||||
(null!=b.children||null!=b.dangerouslySetInnerHTML))throw Error(m(137,a));if(null!=b.dangerouslySetInnerHTML){if(null!=b.children)throw Error(m(60));if("object"!==typeof b.dangerouslySetInnerHTML||!("__html"in b.dangerouslySetInnerHTML))throw Error(m(61));}if(null!=b.style&&"object"!==typeof b.style)throw Error(m(62));}}function qe(a,b){if(-1===a.indexOf("-"))return"string"===typeof b.is;switch(a){case "annotation-xml":case "color-profile":case "font-face":case "font-face-src":case "font-face-uri":case "font-face-format":case "font-face-name":case "missing-glyph":return!1;
|
||||
default:return!0}}function re(a){a=a.target||a.srcElement||window;a.correspondingUseElement&&(a=a.correspondingUseElement);return 3===a.nodeType?a.parentNode:a}function tg(a){if(a=ec(a)){if("function"!==typeof se)throw Error(m(280));var b=a.stateNode;b&&(b=Rc(b),se(a.stateNode,a.type,b))}}function ug(a){Eb?Fb?Fb.push(a):Fb=[a]:Eb=a}function vg(){if(Eb){var a=Eb,b=Fb;Fb=Eb=null;tg(a);if(b)for(a=0;a<b.length;a++)tg(b[a])}}function wg(a,b,c){if(te)return a(b,c);te=!0;try{return xg(a,b,c)}finally{if(te=
|
||||
!1,null!==Eb||null!==Fb)yg(),vg()}}function fc(a,b){var c=a.stateNode;if(null===c)return null;var d=Rc(c);if(null===d)return null;c=d[b];a:switch(b){case "onClick":case "onClickCapture":case "onDoubleClick":case "onDoubleClickCapture":case "onMouseDown":case "onMouseDownCapture":case "onMouseMove":case "onMouseMoveCapture":case "onMouseUp":case "onMouseUpCapture":case "onMouseEnter":(d=!d.disabled)||(a=a.type,d=!("button"===a||"input"===a||"select"===a||"textarea"===a));a=!d;break a;default:a=!1}if(a)return null;
|
||||
if(c&&"function"!==typeof c)throw Error(m(231,b,typeof c));return c}function jj(a,b,c,d,e,f,g,h,k){gc=!1;Sc=null;kj.apply(lj,arguments)}function mj(a,b,c,d,e,f,g,h,k){jj.apply(this,arguments);if(gc){if(gc){var n=Sc;gc=!1;Sc=null}else throw Error(m(198));Tc||(Tc=!0,ue=n)}}function nb(a){var b=a,c=a;if(a.alternate)for(;b.return;)b=b.return;else{a=b;do b=a,0!==(b.flags&4098)&&(c=b.return),a=b.return;while(a)}return 3===b.tag?c:null}function zg(a){if(13===a.tag){var b=a.memoizedState;null===b&&(a=a.alternate,
|
||||
null!==a&&(b=a.memoizedState));if(null!==b)return b.dehydrated}return null}function Ag(a){if(nb(a)!==a)throw Error(m(188));}function nj(a){var b=a.alternate;if(!b){b=nb(a);if(null===b)throw Error(m(188));return b!==a?null:a}for(var c=a,d=b;;){var e=c.return;if(null===e)break;var f=e.alternate;if(null===f){d=e.return;if(null!==d){c=d;continue}break}if(e.child===f.child){for(f=e.child;f;){if(f===c)return Ag(e),a;if(f===d)return Ag(e),b;f=f.sibling}throw Error(m(188));}if(c.return!==d.return)c=e,d=f;
|
||||
else{for(var g=!1,h=e.child;h;){if(h===c){g=!0;c=e;d=f;break}if(h===d){g=!0;d=e;c=f;break}h=h.sibling}if(!g){for(h=f.child;h;){if(h===c){g=!0;c=f;d=e;break}if(h===d){g=!0;d=f;c=e;break}h=h.sibling}if(!g)throw Error(m(189));}}if(c.alternate!==d)throw Error(m(190));}if(3!==c.tag)throw Error(m(188));return c.stateNode.current===c?a:b}function Bg(a){a=nj(a);return null!==a?Cg(a):null}function Cg(a){if(5===a.tag||6===a.tag)return a;for(a=a.child;null!==a;){var b=Cg(a);if(null!==b)return b;a=a.sibling}return null}
|
||||
function oj(a,b){if(Ca&&"function"===typeof Ca.onCommitFiberRoot)try{Ca.onCommitFiberRoot(Uc,a,void 0,128===(a.current.flags&128))}catch(c){}}function pj(a){a>>>=0;return 0===a?32:31-(qj(a)/rj|0)|0}function hc(a){switch(a&-a){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return a&
|
||||
4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return a&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return a}}function Vc(a,b){var c=a.pendingLanes;if(0===c)return 0;var d=0,e=a.suspendedLanes,f=a.pingedLanes,g=c&268435455;if(0!==g){var h=g&~e;0!==h?d=hc(h):(f&=g,0!==f&&(d=hc(f)))}else g=c&~e,0!==g?d=hc(g):0!==f&&(d=hc(f));if(0===d)return 0;if(0!==b&&b!==d&&0===(b&e)&&
|
||||
(e=d&-d,f=b&-b,e>=f||16===e&&0!==(f&4194240)))return b;0!==(d&4)&&(d|=c&16);b=a.entangledLanes;if(0!==b)for(a=a.entanglements,b&=d;0<b;)c=31-ta(b),e=1<<c,d|=a[c],b&=~e;return d}function sj(a,b){switch(a){case 1:case 2:case 4:return b+250;case 8:case 16:case 32:case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return b+5E3;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return-1;
|
||||
case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function tj(a,b){for(var c=a.suspendedLanes,d=a.pingedLanes,e=a.expirationTimes,f=a.pendingLanes;0<f;){var g=31-ta(f),h=1<<g,k=e[g];if(-1===k){if(0===(h&c)||0!==(h&d))e[g]=sj(h,b)}else k<=b&&(a.expiredLanes|=h);f&=~h}}function ve(a){a=a.pendingLanes&-1073741825;return 0!==a?a:a&1073741824?1073741824:0}function Dg(){var a=Wc;Wc<<=1;0===(Wc&4194240)&&(Wc=64);return a}function we(a){for(var b=[],c=0;31>c;c++)b.push(a);
|
||||
return b}function ic(a,b,c){a.pendingLanes|=b;536870912!==b&&(a.suspendedLanes=0,a.pingedLanes=0);a=a.eventTimes;b=31-ta(b);a[b]=c}function uj(a,b){var c=a.pendingLanes&~b;a.pendingLanes=b;a.suspendedLanes=0;a.pingedLanes=0;a.expiredLanes&=b;a.mutableReadLanes&=b;a.entangledLanes&=b;b=a.entanglements;var d=a.eventTimes;for(a=a.expirationTimes;0<c;){var e=31-ta(c),f=1<<e;b[e]=0;d[e]=-1;a[e]=-1;c&=~f}}function xe(a,b){var c=a.entangledLanes|=b;for(a=a.entanglements;c;){var d=31-ta(c),e=1<<d;e&b|a[d]&
|
||||
b&&(a[d]|=b);c&=~e}}function Eg(a){a&=-a;return 1<a?4<a?0!==(a&268435455)?16:536870912:4:1}function Fg(a,b){switch(a){case "focusin":case "focusout":Va=null;break;case "dragenter":case "dragleave":Wa=null;break;case "mouseover":case "mouseout":Xa=null;break;case "pointerover":case "pointerout":jc.delete(b.pointerId);break;case "gotpointercapture":case "lostpointercapture":kc.delete(b.pointerId)}}function lc(a,b,c,d,e,f){if(null===a||a.nativeEvent!==f)return a={blockedOn:b,domEventName:c,eventSystemFlags:d,
|
||||
nativeEvent:f,targetContainers:[e]},null!==b&&(b=ec(b),null!==b&&Gg(b)),a;a.eventSystemFlags|=d;b=a.targetContainers;null!==e&&-1===b.indexOf(e)&&b.push(e);return a}function vj(a,b,c,d,e){switch(b){case "focusin":return Va=lc(Va,a,b,c,d,e),!0;case "dragenter":return Wa=lc(Wa,a,b,c,d,e),!0;case "mouseover":return Xa=lc(Xa,a,b,c,d,e),!0;case "pointerover":var f=e.pointerId;jc.set(f,lc(jc.get(f)||null,a,b,c,d,e));return!0;case "gotpointercapture":return f=e.pointerId,kc.set(f,lc(kc.get(f)||null,a,b,
|
||||
c,d,e)),!0}return!1}function Hg(a){var b=ob(a.target);if(null!==b){var c=nb(b);if(null!==c)if(b=c.tag,13===b){if(b=zg(c),null!==b){a.blockedOn=b;wj(a.priority,function(){xj(c)});return}}else if(3===b&&c.stateNode.current.memoizedState.isDehydrated){a.blockedOn=3===c.tag?c.stateNode.containerInfo:null;return}}a.blockedOn=null}function Xc(a){if(null!==a.blockedOn)return!1;for(var b=a.targetContainers;0<b.length;){var c=ye(a.domEventName,a.eventSystemFlags,b[0],a.nativeEvent);if(null===c){c=a.nativeEvent;
|
||||
var d=new c.constructor(c.type,c);ze=d;c.target.dispatchEvent(d);ze=null}else return b=ec(c),null!==b&&Gg(b),a.blockedOn=c,!1;b.shift()}return!0}function Ig(a,b,c){Xc(a)&&c.delete(b)}function yj(){Ae=!1;null!==Va&&Xc(Va)&&(Va=null);null!==Wa&&Xc(Wa)&&(Wa=null);null!==Xa&&Xc(Xa)&&(Xa=null);jc.forEach(Ig);kc.forEach(Ig)}function mc(a,b){a.blockedOn===b&&(a.blockedOn=null,Ae||(Ae=!0,Jg(Kg,yj)))}function nc(a){if(0<Yc.length){mc(Yc[0],a);for(var b=1;b<Yc.length;b++){var c=Yc[b];c.blockedOn===a&&(c.blockedOn=
|
||||
null)}}null!==Va&&mc(Va,a);null!==Wa&&mc(Wa,a);null!==Xa&&mc(Xa,a);b=function(b){return mc(b,a)};jc.forEach(b);kc.forEach(b);for(b=0;b<Ya.length;b++)c=Ya[b],c.blockedOn===a&&(c.blockedOn=null);for(;0<Ya.length&&(b=Ya[0],null===b.blockedOn);)Hg(b),null===b.blockedOn&&Ya.shift()}function zj(a,b,c,d){var e=z,f=Gb.transition;Gb.transition=null;try{z=1,Be(a,b,c,d)}finally{z=e,Gb.transition=f}}function Aj(a,b,c,d){var e=z,f=Gb.transition;Gb.transition=null;try{z=4,Be(a,b,c,d)}finally{z=e,Gb.transition=
|
||||
f}}function Be(a,b,c,d){if(Zc){var e=ye(a,b,c,d);if(null===e)Ce(a,b,d,$c,c),Fg(a,d);else if(vj(e,a,b,c,d))d.stopPropagation();else if(Fg(a,d),b&4&&-1<Bj.indexOf(a)){for(;null!==e;){var f=ec(e);null!==f&&Cj(f);f=ye(a,b,c,d);null===f&&Ce(a,b,d,$c,c);if(f===e)break;e=f}null!==e&&d.stopPropagation()}else Ce(a,b,d,null,c)}}function ye(a,b,c,d){$c=null;a=re(d);a=ob(a);if(null!==a)if(b=nb(a),null===b)a=null;else if(c=b.tag,13===c){a=zg(b);if(null!==a)return a;a=null}else if(3===c){if(b.stateNode.current.memoizedState.isDehydrated)return 3===
|
||||
b.tag?b.stateNode.containerInfo:null;a=null}else b!==a&&(a=null);$c=a;return null}function Lg(a){switch(a){case "cancel":case "click":case "close":case "contextmenu":case "copy":case "cut":case "auxclick":case "dblclick":case "dragend":case "dragstart":case "drop":case "focusin":case "focusout":case "input":case "invalid":case "keydown":case "keypress":case "keyup":case "mousedown":case "mouseup":case "paste":case "pause":case "play":case "pointercancel":case "pointerdown":case "pointerup":case "ratechange":case "reset":case "resize":case "seeked":case "submit":case "touchcancel":case "touchend":case "touchstart":case "volumechange":case "change":case "selectionchange":case "textInput":case "compositionstart":case "compositionend":case "compositionupdate":case "beforeblur":case "afterblur":case "beforeinput":case "blur":case "fullscreenchange":case "focus":case "hashchange":case "popstate":case "select":case "selectstart":return 1;
|
||||
case "drag":case "dragenter":case "dragexit":case "dragleave":case "dragover":case "mousemove":case "mouseout":case "mouseover":case "pointermove":case "pointerout":case "pointerover":case "scroll":case "toggle":case "touchmove":case "wheel":case "mouseenter":case "mouseleave":case "pointerenter":case "pointerleave":return 4;case "message":switch(Dj()){case De:return 1;case Mg:return 4;case ad:case Ej:return 16;case Ng:return 536870912;default:return 16}default:return 16}}function Og(){if(bd)return bd;
|
||||
var a,b=Ee,c=b.length,d,e="value"in Za?Za.value:Za.textContent,f=e.length;for(a=0;a<c&&b[a]===e[a];a++);var g=c-a;for(d=1;d<=g&&b[c-d]===e[f-d];d++);return bd=e.slice(a,1<d?1-d:void 0)}function cd(a){var b=a.keyCode;"charCode"in a?(a=a.charCode,0===a&&13===b&&(a=13)):a=b;10===a&&(a=13);return 32<=a||13===a?a:0}function dd(){return!0}function Pg(){return!1}function ka(a){function b(b,d,e,f,g){this._reactName=b;this._targetInst=e;this.type=d;this.nativeEvent=f;this.target=g;this.currentTarget=null;
|
||||
for(var c in a)a.hasOwnProperty(c)&&(b=a[c],this[c]=b?b(f):f[c]);this.isDefaultPrevented=(null!=f.defaultPrevented?f.defaultPrevented:!1===f.returnValue)?dd:Pg;this.isPropagationStopped=Pg;return this}E(b.prototype,{preventDefault:function(){this.defaultPrevented=!0;var a=this.nativeEvent;a&&(a.preventDefault?a.preventDefault():"unknown"!==typeof a.returnValue&&(a.returnValue=!1),this.isDefaultPrevented=dd)},stopPropagation:function(){var a=this.nativeEvent;a&&(a.stopPropagation?a.stopPropagation():
|
||||
"unknown"!==typeof a.cancelBubble&&(a.cancelBubble=!0),this.isPropagationStopped=dd)},persist:function(){},isPersistent:dd});return b}function Fj(a){var b=this.nativeEvent;return b.getModifierState?b.getModifierState(a):(a=Gj[a])?!!b[a]:!1}function Fe(a){return Fj}function Qg(a,b){switch(a){case "keyup":return-1!==Hj.indexOf(b.keyCode);case "keydown":return 229!==b.keyCode;case "keypress":case "mousedown":case "focusout":return!0;default:return!1}}function Rg(a){a=a.detail;return"object"===typeof a&&
|
||||
"data"in a?a.data:null}function Ij(a,b){switch(a){case "compositionend":return Rg(b);case "keypress":if(32!==b.which)return null;Sg=!0;return Tg;case "textInput":return a=b.data,a===Tg&&Sg?null:a;default:return null}}function Jj(a,b){if(Hb)return"compositionend"===a||!Ge&&Qg(a,b)?(a=Og(),bd=Ee=Za=null,Hb=!1,a):null;switch(a){case "paste":return null;case "keypress":if(!(b.ctrlKey||b.altKey||b.metaKey)||b.ctrlKey&&b.altKey){if(b.char&&1<b.char.length)return b.char;if(b.which)return String.fromCharCode(b.which)}return null;
|
||||
case "compositionend":return Ug&&"ko"!==b.locale?null:b.data;default:return null}}function Vg(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return"input"===b?!!Kj[a.type]:"textarea"===b?!0:!1}function Lj(a){if(!Ia)return!1;a="on"+a;var b=a in document;b||(b=document.createElement("div"),b.setAttribute(a,"return;"),b="function"===typeof b[a]);return b}function Wg(a,b,c,d){ug(d);b=ed(b,"onChange");0<b.length&&(c=new He("onChange","change",null,c,d),a.push({event:c,listeners:b}))}function Mj(a){Xg(a,
|
||||
0)}function fd(a){var b=Ib(a);if(jg(b))return a}function Nj(a,b){if("change"===a)return b}function Yg(){oc&&(oc.detachEvent("onpropertychange",Zg),pc=oc=null)}function Zg(a){if("value"===a.propertyName&&fd(pc)){var b=[];Wg(b,pc,a,re(a));wg(Mj,b)}}function Oj(a,b,c){"focusin"===a?(Yg(),oc=b,pc=c,oc.attachEvent("onpropertychange",Zg)):"focusout"===a&&Yg()}function Pj(a,b){if("selectionchange"===a||"keyup"===a||"keydown"===a)return fd(pc)}function Qj(a,b){if("click"===a)return fd(b)}function Rj(a,b){if("input"===
|
||||
a||"change"===a)return fd(b)}function Sj(a,b){return a===b&&(0!==a||1/a===1/b)||a!==a&&b!==b}function qc(a,b){if(ua(a,b))return!0;if("object"!==typeof a||null===a||"object"!==typeof b||null===b)return!1;var c=Object.keys(a),d=Object.keys(b);if(c.length!==d.length)return!1;for(d=0;d<c.length;d++){var e=c[d];if(!Zd.call(b,e)||!ua(a[e],b[e]))return!1}return!0}function $g(a){for(;a&&a.firstChild;)a=a.firstChild;return a}function ah(a,b){var c=$g(a);a=0;for(var d;c;){if(3===c.nodeType){d=a+c.textContent.length;
|
||||
if(a<=b&&d>=b)return{node:c,offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=$g(c)}}function bh(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?bh(a,b.parentNode):"contains"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1}function ch(){for(var a=window,b=Qc();b instanceof a.HTMLIFrameElement;){try{var c="string"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break;
|
||||
b=Qc(a.document)}return b}function Ie(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&("input"===b&&("text"===a.type||"search"===a.type||"tel"===a.type||"url"===a.type||"password"===a.type)||"textarea"===b||"true"===a.contentEditable)}function Tj(a){var b=ch(),c=a.focusedElem,d=a.selectionRange;if(b!==c&&c&&c.ownerDocument&&bh(c.ownerDocument.documentElement,c)){if(null!==d&&Ie(c))if(b=d.start,a=d.end,void 0===a&&(a=b),"selectionStart"in c)c.selectionStart=b,c.selectionEnd=Math.min(a,c.value.length);
|
||||
else if(a=(b=c.ownerDocument||document)&&b.defaultView||window,a.getSelection){a=a.getSelection();var e=c.textContent.length,f=Math.min(d.start,e);d=void 0===d.end?f:Math.min(d.end,e);!a.extend&&f>d&&(e=d,d=f,f=e);e=ah(c,f);var g=ah(c,d);e&&g&&(1!==a.rangeCount||a.anchorNode!==e.node||a.anchorOffset!==e.offset||a.focusNode!==g.node||a.focusOffset!==g.offset)&&(b=b.createRange(),b.setStart(e.node,e.offset),a.removeAllRanges(),f>d?(a.addRange(b),a.extend(g.node,g.offset)):(b.setEnd(g.node,g.offset),
|
||||
a.addRange(b)))}b=[];for(a=c;a=a.parentNode;)1===a.nodeType&&b.push({element:a,left:a.scrollLeft,top:a.scrollTop});"function"===typeof c.focus&&c.focus();for(c=0;c<b.length;c++)a=b[c],a.element.scrollLeft=a.left,a.element.scrollTop=a.top}}function dh(a,b,c){var d=c.window===c?c.document:9===c.nodeType?c:c.ownerDocument;Je||null==Jb||Jb!==Qc(d)||(d=Jb,"selectionStart"in d&&Ie(d)?d={start:d.selectionStart,end:d.selectionEnd}:(d=(d.ownerDocument&&d.ownerDocument.defaultView||window).getSelection(),d=
|
||||
{anchorNode:d.anchorNode,anchorOffset:d.anchorOffset,focusNode:d.focusNode,focusOffset:d.focusOffset}),rc&&qc(rc,d)||(rc=d,d=ed(Ke,"onSelect"),0<d.length&&(b=new He("onSelect","select",null,b,c),a.push({event:b,listeners:d}),b.target=Jb)))}function gd(a,b){var c={};c[a.toLowerCase()]=b.toLowerCase();c["Webkit"+a]="webkit"+b;c["Moz"+a]="moz"+b;return c}function hd(a){if(Le[a])return Le[a];if(!Kb[a])return a;var b=Kb[a],c;for(c in b)if(b.hasOwnProperty(c)&&c in eh)return Le[a]=b[c];return a}function $a(a,
|
||||
b){fh.set(a,b);mb(b,[a])}function gh(a,b,c){var d=a.type||"unknown-event";a.currentTarget=c;mj(d,b,void 0,a);a.currentTarget=null}function Xg(a,b){b=0!==(b&4);for(var c=0;c<a.length;c++){var d=a[c],e=d.event;d=d.listeners;a:{var f=void 0;if(b)for(var g=d.length-1;0<=g;g--){var h=d[g],k=h.instance,n=h.currentTarget;h=h.listener;if(k!==f&&e.isPropagationStopped())break a;gh(e,h,n);f=k}else for(g=0;g<d.length;g++){h=d[g];k=h.instance;n=h.currentTarget;h=h.listener;if(k!==f&&e.isPropagationStopped())break a;
|
||||
gh(e,h,n);f=k}}}if(Tc)throw a=ue,Tc=!1,ue=null,a;}function B(a,b){var c=b[Me];void 0===c&&(c=b[Me]=new Set);var d=a+"__bubble";c.has(d)||(hh(b,a,2,!1),c.add(d))}function Ne(a,b,c){var d=0;b&&(d|=4);hh(c,a,d,b)}function sc(a){if(!a[id]){a[id]=!0;cg.forEach(function(b){"selectionchange"!==b&&(Uj.has(b)||Ne(b,!1,a),Ne(b,!0,a))});var b=9===a.nodeType?a:a.ownerDocument;null===b||b[id]||(b[id]=!0,Ne("selectionchange",!1,b))}}function hh(a,b,c,d,e){switch(Lg(b)){case 1:e=zj;break;case 4:e=Aj;break;default:e=
|
||||
Be}c=e.bind(null,b,c,a);e=void 0;!Oe||"touchstart"!==b&&"touchmove"!==b&&"wheel"!==b||(e=!0);d?void 0!==e?a.addEventListener(b,c,{capture:!0,passive:e}):a.addEventListener(b,c,!0):void 0!==e?a.addEventListener(b,c,{passive:e}):a.addEventListener(b,c,!1)}function Ce(a,b,c,d,e){var f=d;if(0===(b&1)&&0===(b&2)&&null!==d)a:for(;;){if(null===d)return;var g=d.tag;if(3===g||4===g){var h=d.stateNode.containerInfo;if(h===e||8===h.nodeType&&h.parentNode===e)break;if(4===g)for(g=d.return;null!==g;){var k=g.tag;
|
||||
if(3===k||4===k)if(k=g.stateNode.containerInfo,k===e||8===k.nodeType&&k.parentNode===e)return;g=g.return}for(;null!==h;){g=ob(h);if(null===g)return;k=g.tag;if(5===k||6===k){d=f=g;continue a}h=h.parentNode}}d=d.return}wg(function(){var d=f,e=re(c),g=[];a:{var h=fh.get(a);if(void 0!==h){var k=He,m=a;switch(a){case "keypress":if(0===cd(c))break a;case "keydown":case "keyup":k=Vj;break;case "focusin":m="focus";k=Pe;break;case "focusout":m="blur";k=Pe;break;case "beforeblur":case "afterblur":k=Pe;break;
|
||||
case "click":if(2===c.button)break a;case "auxclick":case "dblclick":case "mousedown":case "mousemove":case "mouseup":case "mouseout":case "mouseover":case "contextmenu":k=ih;break;case "drag":case "dragend":case "dragenter":case "dragexit":case "dragleave":case "dragover":case "dragstart":case "drop":k=Wj;break;case "touchcancel":case "touchend":case "touchmove":case "touchstart":k=Xj;break;case jh:case kh:case lh:k=Yj;break;case mh:k=Zj;break;case "scroll":k=ak;break;case "wheel":k=bk;break;case "copy":case "cut":case "paste":k=
|
||||
ck;break;case "gotpointercapture":case "lostpointercapture":case "pointercancel":case "pointerdown":case "pointermove":case "pointerout":case "pointerover":case "pointerup":k=nh}var l=0!==(b&4),p=!l&&"scroll"===a,w=l?null!==h?h+"Capture":null:h;l=[];for(var A=d,t;null!==A;){t=A;var M=t.stateNode;5===t.tag&&null!==M&&(t=M,null!==w&&(M=fc(A,w),null!=M&&l.push(tc(A,M,t))));if(p)break;A=A.return}0<l.length&&(h=new k(h,m,null,c,e),g.push({event:h,listeners:l}))}}if(0===(b&7)){a:{h="mouseover"===a||"pointerover"===
|
||||
a;k="mouseout"===a||"pointerout"===a;if(h&&c!==ze&&(m=c.relatedTarget||c.fromElement)&&(ob(m)||m[Ja]))break a;if(k||h){h=e.window===e?e:(h=e.ownerDocument)?h.defaultView||h.parentWindow:window;if(k){if(m=c.relatedTarget||c.toElement,k=d,m=m?ob(m):null,null!==m&&(p=nb(m),m!==p||5!==m.tag&&6!==m.tag))m=null}else k=null,m=d;if(k!==m){l=ih;M="onMouseLeave";w="onMouseEnter";A="mouse";if("pointerout"===a||"pointerover"===a)l=nh,M="onPointerLeave",w="onPointerEnter",A="pointer";p=null==k?h:Ib(k);t=null==
|
||||
m?h:Ib(m);h=new l(M,A+"leave",k,c,e);h.target=p;h.relatedTarget=t;M=null;ob(e)===d&&(l=new l(w,A+"enter",m,c,e),l.target=t,l.relatedTarget=p,M=l);p=M;if(k&&m)b:{l=k;w=m;A=0;for(t=l;t;t=Lb(t))A++;t=0;for(M=w;M;M=Lb(M))t++;for(;0<A-t;)l=Lb(l),A--;for(;0<t-A;)w=Lb(w),t--;for(;A--;){if(l===w||null!==w&&l===w.alternate)break b;l=Lb(l);w=Lb(w)}l=null}else l=null;null!==k&&oh(g,h,k,l,!1);null!==m&&null!==p&&oh(g,p,m,l,!0)}}}a:{h=d?Ib(d):window;k=h.nodeName&&h.nodeName.toLowerCase();if("select"===k||"input"===
|
||||
k&&"file"===h.type)var ma=Nj;else if(Vg(h))if(ph)ma=Rj;else{ma=Pj;var va=Oj}else(k=h.nodeName)&&"input"===k.toLowerCase()&&("checkbox"===h.type||"radio"===h.type)&&(ma=Qj);if(ma&&(ma=ma(a,d))){Wg(g,ma,c,e);break a}va&&va(a,h,d);"focusout"===a&&(va=h._wrapperState)&&va.controlled&&"number"===h.type&&me(h,"number",h.value)}va=d?Ib(d):window;switch(a){case "focusin":if(Vg(va)||"true"===va.contentEditable)Jb=va,Ke=d,rc=null;break;case "focusout":rc=Ke=Jb=null;break;case "mousedown":Je=!0;break;case "contextmenu":case "mouseup":case "dragend":Je=
|
||||
!1;dh(g,c,e);break;case "selectionchange":if(dk)break;case "keydown":case "keyup":dh(g,c,e)}var ab;if(Ge)b:{switch(a){case "compositionstart":var da="onCompositionStart";break b;case "compositionend":da="onCompositionEnd";break b;case "compositionupdate":da="onCompositionUpdate";break b}da=void 0}else Hb?Qg(a,c)&&(da="onCompositionEnd"):"keydown"===a&&229===c.keyCode&&(da="onCompositionStart");da&&(Ug&&"ko"!==c.locale&&(Hb||"onCompositionStart"!==da?"onCompositionEnd"===da&&Hb&&(ab=Og()):(Za=e,Ee=
|
||||
"value"in Za?Za.value:Za.textContent,Hb=!0)),va=ed(d,da),0<va.length&&(da=new qh(da,a,null,c,e),g.push({event:da,listeners:va}),ab?da.data=ab:(ab=Rg(c),null!==ab&&(da.data=ab))));if(ab=ek?Ij(a,c):Jj(a,c))d=ed(d,"onBeforeInput"),0<d.length&&(e=new fk("onBeforeInput","beforeinput",null,c,e),g.push({event:e,listeners:d}),e.data=ab)}Xg(g,b)})}function tc(a,b,c){return{instance:a,listener:b,currentTarget:c}}function ed(a,b){for(var c=b+"Capture",d=[];null!==a;){var e=a,f=e.stateNode;5===e.tag&&null!==
|
||||
f&&(e=f,f=fc(a,c),null!=f&&d.unshift(tc(a,f,e)),f=fc(a,b),null!=f&&d.push(tc(a,f,e)));a=a.return}return d}function Lb(a){if(null===a)return null;do a=a.return;while(a&&5!==a.tag);return a?a:null}function oh(a,b,c,d,e){for(var f=b._reactName,g=[];null!==c&&c!==d;){var h=c,k=h.alternate,n=h.stateNode;if(null!==k&&k===d)break;5===h.tag&&null!==n&&(h=n,e?(k=fc(c,f),null!=k&&g.unshift(tc(c,k,h))):e||(k=fc(c,f),null!=k&&g.push(tc(c,k,h))));c=c.return}0!==g.length&&a.push({event:b,listeners:g})}function rh(a){return("string"===
|
||||
typeof a?a:""+a).replace(gk,"\n").replace(hk,"")}function jd(a,b,c,d){b=rh(b);if(rh(a)!==b&&c)throw Error(m(425));}function kd(){}function Qe(a,b){return"textarea"===a||"noscript"===a||"string"===typeof b.children||"number"===typeof b.children||"object"===typeof b.dangerouslySetInnerHTML&&null!==b.dangerouslySetInnerHTML&&null!=b.dangerouslySetInnerHTML.__html}function ik(a){setTimeout(function(){throw a;})}function Re(a,b){var c=b,d=0;do{var e=c.nextSibling;a.removeChild(c);if(e&&8===e.nodeType)if(c=
|
||||
e.data,"/$"===c){if(0===d){a.removeChild(e);nc(b);return}d--}else"$"!==c&&"$?"!==c&&"$!"!==c||d++;c=e}while(c);nc(b)}function Ka(a){for(;null!=a;a=a.nextSibling){var b=a.nodeType;if(1===b||3===b)break;if(8===b){b=a.data;if("$"===b||"$!"===b||"$?"===b)break;if("/$"===b)return null}}return a}function sh(a){a=a.previousSibling;for(var b=0;a;){if(8===a.nodeType){var c=a.data;if("$"===c||"$!"===c||"$?"===c){if(0===b)return a;b--}else"/$"===c&&b++}a=a.previousSibling}return null}function ob(a){var b=a[Da];
|
||||
if(b)return b;for(var c=a.parentNode;c;){if(b=c[Ja]||c[Da]){c=b.alternate;if(null!==b.child||null!==c&&null!==c.child)for(a=sh(a);null!==a;){if(c=a[Da])return c;a=sh(a)}return b}a=c;c=a.parentNode}return null}function ec(a){a=a[Da]||a[Ja];return!a||5!==a.tag&&6!==a.tag&&13!==a.tag&&3!==a.tag?null:a}function Ib(a){if(5===a.tag||6===a.tag)return a.stateNode;throw Error(m(33));}function Rc(a){return a[uc]||null}function bb(a){return{current:a}}function v(a,b){0>Mb||(a.current=Se[Mb],Se[Mb]=null,Mb--)}
|
||||
function y(a,b,c){Mb++;Se[Mb]=a.current;a.current=b}function Nb(a,b){var c=a.type.contextTypes;if(!c)return cb;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function ea(a){a=a.childContextTypes;return null!==a&&void 0!==a}function th(a,b,c){if(J.current!==cb)throw Error(m(168));
|
||||
y(J,b);y(S,c)}function uh(a,b,c){var d=a.stateNode;b=b.childContextTypes;if("function"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in b))throw Error(m(108,gj(a)||"Unknown",e));return E({},c,d)}function ld(a){a=(a=a.stateNode)&&a.__reactInternalMemoizedMergedChildContext||cb;pb=J.current;y(J,a);y(S,S.current);return!0}function vh(a,b,c){var d=a.stateNode;if(!d)throw Error(m(169));c?(a=uh(a,b,pb),d.__reactInternalMemoizedMergedChildContext=a,v(S),v(J),y(J,a)):v(S);
|
||||
y(S,c)}function wh(a){null===La?La=[a]:La.push(a)}function jk(a){md=!0;wh(a)}function db(){if(!Te&&null!==La){Te=!0;var a=0,b=z;try{var c=La;for(z=1;a<c.length;a++){var d=c[a];do d=d(!0);while(null!==d)}La=null;md=!1}catch(e){throw null!==La&&(La=La.slice(a+1)),xh(De,db),e;}finally{z=b,Te=!1}}return null}function qb(a,b){Ob[Pb++]=nd;Ob[Pb++]=od;od=a;nd=b}function yh(a,b,c){na[oa++]=Ma;na[oa++]=Na;na[oa++]=rb;rb=a;var d=Ma;a=Na;var e=32-ta(d)-1;d&=~(1<<e);c+=1;var f=32-ta(b)+e;if(30<f){var g=e-e%5;
|
||||
f=(d&(1<<g)-1).toString(32);d>>=g;e-=g;Ma=1<<32-ta(b)+e|c<<e|d;Na=f+a}else Ma=1<<f|c<<e|d,Na=a}function Ue(a){null!==a.return&&(qb(a,1),yh(a,1,0))}function Ve(a){for(;a===od;)od=Ob[--Pb],Ob[Pb]=null,nd=Ob[--Pb],Ob[Pb]=null;for(;a===rb;)rb=na[--oa],na[oa]=null,Na=na[--oa],na[oa]=null,Ma=na[--oa],na[oa]=null}function zh(a,b){var c=pa(5,null,null,0);c.elementType="DELETED";c.stateNode=b;c.return=a;b=a.deletions;null===b?(a.deletions=[c],a.flags|=16):b.push(c)}function Ah(a,b){switch(a.tag){case 5:var c=
|
||||
a.type;b=1!==b.nodeType||c.toLowerCase()!==b.nodeName.toLowerCase()?null:b;return null!==b?(a.stateNode=b,la=a,fa=Ka(b.firstChild),!0):!1;case 6:return b=""===a.pendingProps||3!==b.nodeType?null:b,null!==b?(a.stateNode=b,la=a,fa=null,!0):!1;case 13:return b=8!==b.nodeType?null:b,null!==b?(c=null!==rb?{id:Ma,overflow:Na}:null,a.memoizedState={dehydrated:b,treeContext:c,retryLane:1073741824},c=pa(18,null,null,0),c.stateNode=b,c.return=a,a.child=c,la=a,fa=null,!0):!1;default:return!1}}function We(a){return 0!==
|
||||
(a.mode&1)&&0===(a.flags&128)}function Xe(a){if(D){var b=fa;if(b){var c=b;if(!Ah(a,b)){if(We(a))throw Error(m(418));b=Ka(c.nextSibling);var d=la;b&&Ah(a,b)?zh(d,c):(a.flags=a.flags&-4097|2,D=!1,la=a)}}else{if(We(a))throw Error(m(418));a.flags=a.flags&-4097|2;D=!1;la=a}}}function Bh(a){for(a=a.return;null!==a&&5!==a.tag&&3!==a.tag&&13!==a.tag;)a=a.return;la=a}function pd(a){if(a!==la)return!1;if(!D)return Bh(a),D=!0,!1;var b;(b=3!==a.tag)&&!(b=5!==a.tag)&&(b=a.type,b="head"!==b&&"body"!==b&&!Qe(a.type,
|
||||
a.memoizedProps));if(b&&(b=fa)){if(We(a)){for(a=fa;a;)a=Ka(a.nextSibling);throw Error(m(418));}for(;b;)zh(a,b),b=Ka(b.nextSibling)}Bh(a);if(13===a.tag){a=a.memoizedState;a=null!==a?a.dehydrated:null;if(!a)throw Error(m(317));a:{a=a.nextSibling;for(b=0;a;){if(8===a.nodeType){var c=a.data;if("/$"===c){if(0===b){fa=Ka(a.nextSibling);break a}b--}else"$"!==c&&"$!"!==c&&"$?"!==c||b++}a=a.nextSibling}fa=null}}else fa=la?Ka(a.stateNode.nextSibling):null;return!0}function Qb(){fa=la=null;D=!1}function Ye(a){null===
|
||||
wa?wa=[a]:wa.push(a)}function vc(a,b,c){a=c.ref;if(null!==a&&"function"!==typeof a&&"object"!==typeof a){if(c._owner){c=c._owner;if(c){if(1!==c.tag)throw Error(m(309));var d=c.stateNode}if(!d)throw Error(m(147,a));var e=d,f=""+a;if(null!==b&&null!==b.ref&&"function"===typeof b.ref&&b.ref._stringRef===f)return b.ref;b=function(a){var b=e.refs;null===a?delete b[f]:b[f]=a};b._stringRef=f;return b}if("string"!==typeof a)throw Error(m(284));if(!c._owner)throw Error(m(290,a));}return a}function qd(a,b){a=
|
||||
Object.prototype.toString.call(b);throw Error(m(31,"[object Object]"===a?"object with keys {"+Object.keys(b).join(", ")+"}":a));}function Ch(a){var b=a._init;return b(a._payload)}function Dh(a){function b(b,c){if(a){var d=b.deletions;null===d?(b.deletions=[c],b.flags|=16):d.push(c)}}function c(c,d){if(!a)return null;for(;null!==d;)b(c,d),d=d.sibling;return null}function d(a,b){for(a=new Map;null!==b;)null!==b.key?a.set(b.key,b):a.set(b.index,b),b=b.sibling;return a}function e(a,b){a=eb(a,b);a.index=
|
||||
0;a.sibling=null;return a}function f(b,c,d){b.index=d;if(!a)return b.flags|=1048576,c;d=b.alternate;if(null!==d)return d=d.index,d<c?(b.flags|=2,c):d;b.flags|=2;return c}function g(b){a&&null===b.alternate&&(b.flags|=2);return b}function h(a,b,c,d){if(null===b||6!==b.tag)return b=Ze(c,a.mode,d),b.return=a,b;b=e(b,c);b.return=a;return b}function k(a,b,c,d){var f=c.type;if(f===Bb)return l(a,b,c.props.children,d,c.key);if(null!==b&&(b.elementType===f||"object"===typeof f&&null!==f&&f.$$typeof===Ta&&
|
||||
Ch(f)===b.type))return d=e(b,c.props),d.ref=vc(a,b,c),d.return=a,d;d=rd(c.type,c.key,c.props,null,a.mode,d);d.ref=vc(a,b,c);d.return=a;return d}function n(a,b,c,d){if(null===b||4!==b.tag||b.stateNode.containerInfo!==c.containerInfo||b.stateNode.implementation!==c.implementation)return b=$e(c,a.mode,d),b.return=a,b;b=e(b,c.children||[]);b.return=a;return b}function l(a,b,c,d,f){if(null===b||7!==b.tag)return b=sb(c,a.mode,d,f),b.return=a,b;b=e(b,c);b.return=a;return b}function u(a,b,c){if("string"===
|
||||
typeof b&&""!==b||"number"===typeof b)return b=Ze(""+b,a.mode,c),b.return=a,b;if("object"===typeof b&&null!==b){switch(b.$$typeof){case sd:return c=rd(b.type,b.key,b.props,null,a.mode,c),c.ref=vc(a,null,b),c.return=a,c;case Cb:return b=$e(b,a.mode,c),b.return=a,b;case Ta:var d=b._init;return u(a,d(b._payload),c)}if(cc(b)||ac(b))return b=sb(b,a.mode,c,null),b.return=a,b;qd(a,b)}return null}function r(a,b,c,d){var e=null!==b?b.key:null;if("string"===typeof c&&""!==c||"number"===typeof c)return null!==
|
||||
e?null:h(a,b,""+c,d);if("object"===typeof c&&null!==c){switch(c.$$typeof){case sd:return c.key===e?k(a,b,c,d):null;case Cb:return c.key===e?n(a,b,c,d):null;case Ta:return e=c._init,r(a,b,e(c._payload),d)}if(cc(c)||ac(c))return null!==e?null:l(a,b,c,d,null);qd(a,c)}return null}function p(a,b,c,d,e){if("string"===typeof d&&""!==d||"number"===typeof d)return a=a.get(c)||null,h(b,a,""+d,e);if("object"===typeof d&&null!==d){switch(d.$$typeof){case sd:return a=a.get(null===d.key?c:d.key)||null,k(b,a,d,
|
||||
e);case Cb:return a=a.get(null===d.key?c:d.key)||null,n(b,a,d,e);case Ta:var f=d._init;return p(a,b,c,f(d._payload),e)}if(cc(d)||ac(d))return a=a.get(c)||null,l(b,a,d,e,null);qd(b,d)}return null}function x(e,g,h,k){for(var n=null,m=null,l=g,t=g=0,q=null;null!==l&&t<h.length;t++){l.index>t?(q=l,l=null):q=l.sibling;var A=r(e,l,h[t],k);if(null===A){null===l&&(l=q);break}a&&l&&null===A.alternate&&b(e,l);g=f(A,g,t);null===m?n=A:m.sibling=A;m=A;l=q}if(t===h.length)return c(e,l),D&&qb(e,t),n;if(null===l){for(;t<
|
||||
h.length;t++)l=u(e,h[t],k),null!==l&&(g=f(l,g,t),null===m?n=l:m.sibling=l,m=l);D&&qb(e,t);return n}for(l=d(e,l);t<h.length;t++)q=p(l,e,t,h[t],k),null!==q&&(a&&null!==q.alternate&&l.delete(null===q.key?t:q.key),g=f(q,g,t),null===m?n=q:m.sibling=q,m=q);a&&l.forEach(function(a){return b(e,a)});D&&qb(e,t);return n}function I(e,g,h,k){var n=ac(h);if("function"!==typeof n)throw Error(m(150));h=n.call(h);if(null==h)throw Error(m(151));for(var l=n=null,q=g,t=g=0,A=null,w=h.next();null!==q&&!w.done;t++,w=
|
||||
h.next()){q.index>t?(A=q,q=null):A=q.sibling;var x=r(e,q,w.value,k);if(null===x){null===q&&(q=A);break}a&&q&&null===x.alternate&&b(e,q);g=f(x,g,t);null===l?n=x:l.sibling=x;l=x;q=A}if(w.done)return c(e,q),D&&qb(e,t),n;if(null===q){for(;!w.done;t++,w=h.next())w=u(e,w.value,k),null!==w&&(g=f(w,g,t),null===l?n=w:l.sibling=w,l=w);D&&qb(e,t);return n}for(q=d(e,q);!w.done;t++,w=h.next())w=p(q,e,t,w.value,k),null!==w&&(a&&null!==w.alternate&&q.delete(null===w.key?t:w.key),g=f(w,g,t),null===l?n=w:l.sibling=
|
||||
w,l=w);a&&q.forEach(function(a){return b(e,a)});D&&qb(e,t);return n}function v(a,d,f,h){"object"===typeof f&&null!==f&&f.type===Bb&&null===f.key&&(f=f.props.children);if("object"===typeof f&&null!==f){switch(f.$$typeof){case sd:a:{for(var k=f.key,n=d;null!==n;){if(n.key===k){k=f.type;if(k===Bb){if(7===n.tag){c(a,n.sibling);d=e(n,f.props.children);d.return=a;a=d;break a}}else if(n.elementType===k||"object"===typeof k&&null!==k&&k.$$typeof===Ta&&Ch(k)===n.type){c(a,n.sibling);d=e(n,f.props);d.ref=vc(a,
|
||||
n,f);d.return=a;a=d;break a}c(a,n);break}else b(a,n);n=n.sibling}f.type===Bb?(d=sb(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=rd(f.type,f.key,f.props,null,a.mode,h),h.ref=vc(a,d,f),h.return=a,a=h)}return g(a);case Cb:a:{for(n=f.key;null!==d;){if(d.key===n)if(4===d.tag&&d.stateNode.containerInfo===f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[]);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=$e(f,a.mode,h);d.return=a;
|
||||
a=d}return g(a);case Ta:return n=f._init,v(a,d,n(f._payload),h)}if(cc(f))return x(a,d,f,h);if(ac(f))return I(a,d,f,h);qd(a,f)}return"string"===typeof f&&""!==f||"number"===typeof f?(f=""+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f),d.return=a,a=d):(c(a,d),d=Ze(f,a.mode,h),d.return=a,a=d),g(a)):c(a,d)}return v}function af(){bf=Rb=td=null}function cf(a,b){b=ud.current;v(ud);a._currentValue=b}function df(a,b,c){for(;null!==a;){var d=a.alternate;(a.childLanes&b)!==b?(a.childLanes|=b,null!==d&&(d.childLanes|=
|
||||
b)):null!==d&&(d.childLanes&b)!==b&&(d.childLanes|=b);if(a===c)break;a=a.return}}function Sb(a,b){td=a;bf=Rb=null;a=a.dependencies;null!==a&&null!==a.firstContext&&(0!==(a.lanes&b)&&(ha=!0),a.firstContext=null)}function qa(a){var b=a._currentValue;if(bf!==a)if(a={context:a,memoizedValue:b,next:null},null===Rb){if(null===td)throw Error(m(308));Rb=a;td.dependencies={lanes:0,firstContext:a}}else Rb=Rb.next=a;return b}function ef(a){null===tb?tb=[a]:tb.push(a)}function Eh(a,b,c,d){var e=b.interleaved;
|
||||
null===e?(c.next=c,ef(b)):(c.next=e.next,e.next=c);b.interleaved=c;return Oa(a,d)}function Oa(a,b){a.lanes|=b;var c=a.alternate;null!==c&&(c.lanes|=b);c=a;for(a=a.return;null!==a;)a.childLanes|=b,c=a.alternate,null!==c&&(c.childLanes|=b),c=a,a=a.return;return 3===c.tag?c.stateNode:null}function ff(a){a.updateQueue={baseState:a.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Fh(a,b){a=a.updateQueue;b.updateQueue===a&&(b.updateQueue=
|
||||
{baseState:a.baseState,firstBaseUpdate:a.firstBaseUpdate,lastBaseUpdate:a.lastBaseUpdate,shared:a.shared,effects:a.effects})}function Pa(a,b){return{eventTime:a,lane:b,tag:0,payload:null,callback:null,next:null}}function fb(a,b,c){var d=a.updateQueue;if(null===d)return null;d=d.shared;if(0!==(p&2)){var e=d.pending;null===e?b.next=b:(b.next=e.next,e.next=b);d.pending=b;return kk(a,c)}e=d.interleaved;null===e?(b.next=b,ef(d)):(b.next=e.next,e.next=b);d.interleaved=b;return Oa(a,c)}function vd(a,b,c){b=
|
||||
b.updateQueue;if(null!==b&&(b=b.shared,0!==(c&4194240))){var d=b.lanes;d&=a.pendingLanes;c|=d;b.lanes=c;xe(a,c)}}function Gh(a,b){var c=a.updateQueue,d=a.alternate;if(null!==d&&(d=d.updateQueue,c===d)){var e=null,f=null;c=c.firstBaseUpdate;if(null!==c){do{var g={eventTime:c.eventTime,lane:c.lane,tag:c.tag,payload:c.payload,callback:c.callback,next:null};null===f?e=f=g:f=f.next=g;c=c.next}while(null!==c);null===f?e=f=b:f=f.next=b}else e=f=b;c={baseState:d.baseState,firstBaseUpdate:e,lastBaseUpdate:f,
|
||||
shared:d.shared,effects:d.effects};a.updateQueue=c;return}a=c.lastBaseUpdate;null===a?c.firstBaseUpdate=b:a.next=b;c.lastBaseUpdate=b}function wd(a,b,c,d){var e=a.updateQueue;gb=!1;var f=e.firstBaseUpdate,g=e.lastBaseUpdate,h=e.shared.pending;if(null!==h){e.shared.pending=null;var k=h,n=k.next;k.next=null;null===g?f=n:g.next=n;g=k;var l=a.alternate;null!==l&&(l=l.updateQueue,h=l.lastBaseUpdate,h!==g&&(null===h?l.firstBaseUpdate=n:h.next=n,l.lastBaseUpdate=k))}if(null!==f){var m=e.baseState;g=0;l=
|
||||
n=k=null;h=f;do{var r=h.lane,p=h.eventTime;if((d&r)===r){null!==l&&(l=l.next={eventTime:p,lane:0,tag:h.tag,payload:h.payload,callback:h.callback,next:null});a:{var x=a,v=h;r=b;p=c;switch(v.tag){case 1:x=v.payload;if("function"===typeof x){m=x.call(p,m,r);break a}m=x;break a;case 3:x.flags=x.flags&-65537|128;case 0:x=v.payload;r="function"===typeof x?x.call(p,m,r):x;if(null===r||void 0===r)break a;m=E({},m,r);break a;case 2:gb=!0}}null!==h.callback&&0!==h.lane&&(a.flags|=64,r=e.effects,null===r?e.effects=
|
||||
[h]:r.push(h))}else p={eventTime:p,lane:r,tag:h.tag,payload:h.payload,callback:h.callback,next:null},null===l?(n=l=p,k=m):l=l.next=p,g|=r;h=h.next;if(null===h)if(h=e.shared.pending,null===h)break;else r=h,h=r.next,r.next=null,e.lastBaseUpdate=r,e.shared.pending=null}while(1);null===l&&(k=m);e.baseState=k;e.firstBaseUpdate=n;e.lastBaseUpdate=l;b=e.shared.interleaved;if(null!==b){e=b;do g|=e.lane,e=e.next;while(e!==b)}else null===f&&(e.shared.lanes=0);ra|=g;a.lanes=g;a.memoizedState=m}}function Hh(a,
|
||||
b,c){a=b.effects;b.effects=null;if(null!==a)for(b=0;b<a.length;b++){var d=a[b],e=d.callback;if(null!==e){d.callback=null;d=c;if("function"!==typeof e)throw Error(m(191,e));e.call(d)}}}function ub(a){if(a===wc)throw Error(m(174));return a}function gf(a,b){y(xc,b);y(yc,a);y(Ea,wc);a=b.nodeType;switch(a){case 9:case 11:b=(b=b.documentElement)?b.namespaceURI:oe(null,"");break;default:a=8===a?b.parentNode:b,b=a.namespaceURI||null,a=a.tagName,b=oe(b,a)}v(Ea);y(Ea,b)}function Tb(a){v(Ea);v(yc);v(xc)}function Ih(a){ub(xc.current);
|
||||
var b=ub(Ea.current);var c=oe(b,a.type);b!==c&&(y(yc,a),y(Ea,c))}function hf(a){yc.current===a&&(v(Ea),v(yc))}function xd(a){for(var b=a;null!==b;){if(13===b.tag){var c=b.memoizedState;if(null!==c&&(c=c.dehydrated,null===c||"$?"===c.data||"$!"===c.data))return b}else if(19===b.tag&&void 0!==b.memoizedProps.revealOrder){if(0!==(b.flags&128))return b}else if(null!==b.child){b.child.return=b;b=b.child;continue}if(b===a)break;for(;null===b.sibling;){if(null===b.return||b.return===a)return null;b=b.return}b.sibling.return=
|
||||
b.return;b=b.sibling}return null}function jf(){for(var a=0;a<kf.length;a++)kf[a]._workInProgressVersionPrimary=null;kf.length=0}function V(){throw Error(m(321));}function lf(a,b){if(null===b)return!1;for(var c=0;c<b.length&&c<a.length;c++)if(!ua(a[c],b[c]))return!1;return!0}function mf(a,b,c,d,e,f){vb=f;C=b;b.memoizedState=null;b.updateQueue=null;b.lanes=0;yd.current=null===a||null===a.memoizedState?lk:mk;a=c(d,e);if(zc){f=0;do{zc=!1;Ac=0;if(25<=f)throw Error(m(301));f+=1;N=K=null;b.updateQueue=null;
|
||||
yd.current=nk;a=c(d,e)}while(zc)}yd.current=zd;b=null!==K&&null!==K.next;vb=0;N=K=C=null;Ad=!1;if(b)throw Error(m(300));return a}function nf(){var a=0!==Ac;Ac=0;return a}function Fa(){var a={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};null===N?C.memoizedState=N=a:N=N.next=a;return N}function sa(){if(null===K){var a=C.alternate;a=null!==a?a.memoizedState:null}else a=K.next;var b=null===N?C.memoizedState:N.next;if(null!==b)N=b,K=a;else{if(null===a)throw Error(m(310));K=a;
|
||||
a={memoizedState:K.memoizedState,baseState:K.baseState,baseQueue:K.baseQueue,queue:K.queue,next:null};null===N?C.memoizedState=N=a:N=N.next=a}return N}function Bc(a,b){return"function"===typeof b?b(a):b}function of(a,b,c){b=sa();c=b.queue;if(null===c)throw Error(m(311));c.lastRenderedReducer=a;var d=K,e=d.baseQueue,f=c.pending;if(null!==f){if(null!==e){var g=e.next;e.next=f.next;f.next=g}d.baseQueue=e=f;c.pending=null}if(null!==e){f=e.next;d=d.baseState;var h=g=null,k=null,n=f;do{var l=n.lane;if((vb&
|
||||
l)===l)null!==k&&(k=k.next={lane:0,action:n.action,hasEagerState:n.hasEagerState,eagerState:n.eagerState,next:null}),d=n.hasEagerState?n.eagerState:a(d,n.action);else{var u={lane:l,action:n.action,hasEagerState:n.hasEagerState,eagerState:n.eagerState,next:null};null===k?(h=k=u,g=d):k=k.next=u;C.lanes|=l;ra|=l}n=n.next}while(null!==n&&n!==f);null===k?g=d:k.next=h;ua(d,b.memoizedState)||(ha=!0);b.memoizedState=d;b.baseState=g;b.baseQueue=k;c.lastRenderedState=d}a=c.interleaved;if(null!==a){e=a;do f=
|
||||
e.lane,C.lanes|=f,ra|=f,e=e.next;while(e!==a)}else null===e&&(c.lanes=0);return[b.memoizedState,c.dispatch]}function pf(a,b,c){b=sa();c=b.queue;if(null===c)throw Error(m(311));c.lastRenderedReducer=a;var d=c.dispatch,e=c.pending,f=b.memoizedState;if(null!==e){c.pending=null;var g=e=e.next;do f=a(f,g.action),g=g.next;while(g!==e);ua(f,b.memoizedState)||(ha=!0);b.memoizedState=f;null===b.baseQueue&&(b.baseState=f);c.lastRenderedState=f}return[f,d]}function Jh(a,b,c){}function Kh(a,b,c){c=C;var d=sa(),
|
||||
e=b(),f=!ua(d.memoizedState,e);f&&(d.memoizedState=e,ha=!0);d=d.queue;qf(Lh.bind(null,c,d,a),[a]);if(d.getSnapshot!==b||f||null!==N&&N.memoizedState.tag&1){c.flags|=2048;Cc(9,Mh.bind(null,c,d,e,b),void 0,null);if(null===O)throw Error(m(349));0!==(vb&30)||Nh(c,b,e)}return e}function Nh(a,b,c){a.flags|=16384;a={getSnapshot:b,value:c};b=C.updateQueue;null===b?(b={lastEffect:null,stores:null},C.updateQueue=b,b.stores=[a]):(c=b.stores,null===c?b.stores=[a]:c.push(a))}function Mh(a,b,c,d){b.value=c;b.getSnapshot=
|
||||
d;Oh(b)&&Ph(a)}function Lh(a,b,c){return c(function(){Oh(b)&&Ph(a)})}function Oh(a){var b=a.getSnapshot;a=a.value;try{var c=b();return!ua(a,c)}catch(d){return!0}}function Ph(a){var b=Oa(a,1);null!==b&&xa(b,a,1,-1)}function Qh(a){var b=Fa();"function"===typeof a&&(a=a());b.memoizedState=b.baseState=a;a={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:Bc,lastRenderedState:a};b.queue=a;a=a.dispatch=ok.bind(null,C,a);return[b.memoizedState,a]}function Cc(a,b,c,d){a={tag:a,create:b,
|
||||
destroy:c,deps:d,next:null};b=C.updateQueue;null===b?(b={lastEffect:null,stores:null},C.updateQueue=b,b.lastEffect=a.next=a):(c=b.lastEffect,null===c?b.lastEffect=a.next=a:(d=c.next,c.next=a,a.next=d,b.lastEffect=a));return a}function Rh(a){return sa().memoizedState}function Bd(a,b,c,d){var e=Fa();C.flags|=a;e.memoizedState=Cc(1|b,c,void 0,void 0===d?null:d)}function Cd(a,b,c,d){var e=sa();d=void 0===d?null:d;var f=void 0;if(null!==K){var g=K.memoizedState;f=g.destroy;if(null!==d&&lf(d,g.deps)){e.memoizedState=
|
||||
Cc(b,c,f,d);return}}C.flags|=a;e.memoizedState=Cc(1|b,c,f,d)}function Sh(a,b){return Bd(8390656,8,a,b)}function qf(a,b){return Cd(2048,8,a,b)}function Th(a,b){return Cd(4,2,a,b)}function Uh(a,b){return Cd(4,4,a,b)}function Vh(a,b){if("function"===typeof b)return a=a(),b(a),function(){b(null)};if(null!==b&&void 0!==b)return a=a(),b.current=a,function(){b.current=null}}function Wh(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;return Cd(4,4,Vh.bind(null,b,a),c)}function rf(a,b){}function Xh(a,b){var c=
|
||||
sa();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&lf(b,d[1]))return d[0];c.memoizedState=[a,b];return a}function Yh(a,b){var c=sa();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&lf(b,d[1]))return d[0];a=a();c.memoizedState=[a,b];return a}function Zh(a,b,c){if(0===(vb&21))return a.baseState&&(a.baseState=!1,ha=!0),a.memoizedState=c;ua(c,b)||(c=Dg(),C.lanes|=c,ra|=c,a.baseState=!0);return b}function pk(a,b,c){c=z;z=0!==c&&4>c?c:4;a(!0);var d=sf.transition;sf.transition=
|
||||
{};try{a(!1),b()}finally{z=c,sf.transition=d}}function $h(){return sa().memoizedState}function qk(a,b,c){var d=hb(a);c={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(ai(a))bi(b,c);else if(c=Eh(a,b,c,d),null!==c){var e=Z();xa(c,a,d,e);ci(c,b,d)}}function ok(a,b,c){var d=hb(a),e={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(ai(a))bi(b,e);else{var f=a.alternate;if(0===a.lanes&&(null===f||0===f.lanes)&&(f=b.lastRenderedReducer,null!==f))try{var g=b.lastRenderedState,
|
||||
h=f(g,c);e.hasEagerState=!0;e.eagerState=h;if(ua(h,g)){var k=b.interleaved;null===k?(e.next=e,ef(b)):(e.next=k.next,k.next=e);b.interleaved=e;return}}catch(n){}finally{}c=Eh(a,b,e,d);null!==c&&(e=Z(),xa(c,a,d,e),ci(c,b,d))}}function ai(a){var b=a.alternate;return a===C||null!==b&&b===C}function bi(a,b){zc=Ad=!0;var c=a.pending;null===c?b.next=b:(b.next=c.next,c.next=b);a.pending=b}function ci(a,b,c){if(0!==(c&4194240)){var d=b.lanes;d&=a.pendingLanes;c|=d;b.lanes=c;xe(a,c)}}function ya(a,b){if(a&&
|
||||
a.defaultProps){b=E({},b);a=a.defaultProps;for(var c in a)void 0===b[c]&&(b[c]=a[c]);return b}return b}function tf(a,b,c,d){b=a.memoizedState;c=c(d,b);c=null===c||void 0===c?b:E({},b,c);a.memoizedState=c;0===a.lanes&&(a.updateQueue.baseState=c)}function di(a,b,c,d,e,f,g){a=a.stateNode;return"function"===typeof a.shouldComponentUpdate?a.shouldComponentUpdate(d,f,g):b.prototype&&b.prototype.isPureReactComponent?!qc(c,d)||!qc(e,f):!0}function ei(a,b,c){var d=!1,e=cb;var f=b.contextType;"object"===typeof f&&
|
||||
null!==f?f=qa(f):(e=ea(b)?pb:J.current,d=b.contextTypes,f=(d=null!==d&&void 0!==d)?Nb(a,e):cb);b=new b(c,f);a.memoizedState=null!==b.state&&void 0!==b.state?b.state:null;b.updater=Dd;a.stateNode=b;b._reactInternals=a;d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=e,a.__reactInternalMemoizedMaskedChildContext=f);return b}function fi(a,b,c,d){a=b.state;"function"===typeof b.componentWillReceiveProps&&b.componentWillReceiveProps(c,d);"function"===typeof b.UNSAFE_componentWillReceiveProps&&
|
||||
b.UNSAFE_componentWillReceiveProps(c,d);b.state!==a&&Dd.enqueueReplaceState(b,b.state,null)}function uf(a,b,c,d){var e=a.stateNode;e.props=c;e.state=a.memoizedState;e.refs={};ff(a);var f=b.contextType;"object"===typeof f&&null!==f?e.context=qa(f):(f=ea(b)?pb:J.current,e.context=Nb(a,f));e.state=a.memoizedState;f=b.getDerivedStateFromProps;"function"===typeof f&&(tf(a,b,f,c),e.state=a.memoizedState);"function"===typeof b.getDerivedStateFromProps||"function"===typeof e.getSnapshotBeforeUpdate||"function"!==
|
||||
typeof e.UNSAFE_componentWillMount&&"function"!==typeof e.componentWillMount||(b=e.state,"function"===typeof e.componentWillMount&&e.componentWillMount(),"function"===typeof e.UNSAFE_componentWillMount&&e.UNSAFE_componentWillMount(),b!==e.state&&Dd.enqueueReplaceState(e,e.state,null),wd(a,c,e,d),e.state=a.memoizedState);"function"===typeof e.componentDidMount&&(a.flags|=4194308)}function Ub(a,b){try{var c="",d=b;do c+=fj(d),d=d.return;while(d);var e=c}catch(f){e="\nError generating stack: "+f.message+
|
||||
"\n"+f.stack}return{value:a,source:b,stack:e,digest:null}}function vf(a,b,c){return{value:a,source:null,stack:null!=c?c:null,digest:null!=b?b:null}}function wf(a,b){try{console.error(b.value)}catch(c){setTimeout(function(){throw c;})}}function gi(a,b,c){c=Pa(-1,c);c.tag=3;c.payload={element:null};var d=b.value;c.callback=function(){Ed||(Ed=!0,xf=d);wf(a,b)};return c}function hi(a,b,c){c=Pa(-1,c);c.tag=3;var d=a.type.getDerivedStateFromError;if("function"===typeof d){var e=b.value;c.payload=function(){return d(e)};
|
||||
c.callback=function(){wf(a,b)}}var f=a.stateNode;null!==f&&"function"===typeof f.componentDidCatch&&(c.callback=function(){wf(a,b);"function"!==typeof d&&(null===ib?ib=new Set([this]):ib.add(this));var c=b.stack;this.componentDidCatch(b.value,{componentStack:null!==c?c:""})});return c}function ii(a,b,c){var d=a.pingCache;if(null===d){d=a.pingCache=new rk;var e=new Set;d.set(b,e)}else e=d.get(b),void 0===e&&(e=new Set,d.set(b,e));e.has(c)||(e.add(c),a=sk.bind(null,a,b,c),b.then(a,a))}function ji(a){do{var b;
|
||||
if(b=13===a.tag)b=a.memoizedState,b=null!==b?null!==b.dehydrated?!0:!1:!0;if(b)return a;a=a.return}while(null!==a);return null}function ki(a,b,c,d,e){if(0===(a.mode&1))return a===b?a.flags|=65536:(a.flags|=128,c.flags|=131072,c.flags&=-52805,1===c.tag&&(null===c.alternate?c.tag=17:(b=Pa(-1,1),b.tag=2,fb(c,b,1))),c.lanes|=1),a;a.flags|=65536;a.lanes=e;return a}function aa(a,b,c,d){b.child=null===a?li(b,null,c,d):Vb(b,a.child,c,d)}function mi(a,b,c,d,e){c=c.render;var f=b.ref;Sb(b,e);d=mf(a,b,c,d,f,
|
||||
e);c=nf();if(null!==a&&!ha)return b.updateQueue=a.updateQueue,b.flags&=-2053,a.lanes&=~e,Qa(a,b,e);D&&c&&Ue(b);b.flags|=1;aa(a,b,d,e);return b.child}function ni(a,b,c,d,e){if(null===a){var f=c.type;if("function"===typeof f&&!yf(f)&&void 0===f.defaultProps&&null===c.compare&&void 0===c.defaultProps)return b.tag=15,b.type=f,oi(a,b,f,d,e);a=rd(c.type,null,d,b,b.mode,e);a.ref=b.ref;a.return=b;return b.child=a}f=a.child;if(0===(a.lanes&e)){var g=f.memoizedProps;c=c.compare;c=null!==c?c:qc;if(c(g,d)&&a.ref===
|
||||
b.ref)return Qa(a,b,e)}b.flags|=1;a=eb(f,d);a.ref=b.ref;a.return=b;return b.child=a}function oi(a,b,c,d,e){if(null!==a){var f=a.memoizedProps;if(qc(f,d)&&a.ref===b.ref)if(ha=!1,b.pendingProps=d=f,0!==(a.lanes&e))0!==(a.flags&131072)&&(ha=!0);else return b.lanes=a.lanes,Qa(a,b,e)}return zf(a,b,c,d,e)}function pi(a,b,c){var d=b.pendingProps,e=d.children,f=null!==a?a.memoizedState:null;if("hidden"===d.mode)if(0===(b.mode&1))b.memoizedState={baseLanes:0,cachePool:null,transitions:null},y(Ga,ba),ba|=c;
|
||||
else{if(0===(c&1073741824))return a=null!==f?f.baseLanes|c:c,b.lanes=b.childLanes=1073741824,b.memoizedState={baseLanes:a,cachePool:null,transitions:null},b.updateQueue=null,y(Ga,ba),ba|=a,null;b.memoizedState={baseLanes:0,cachePool:null,transitions:null};d=null!==f?f.baseLanes:c;y(Ga,ba);ba|=d}else null!==f?(d=f.baseLanes|c,b.memoizedState=null):d=c,y(Ga,ba),ba|=d;aa(a,b,e,c);return b.child}function qi(a,b){var c=b.ref;if(null===a&&null!==c||null!==a&&a.ref!==c)b.flags|=512,b.flags|=2097152}function zf(a,
|
||||
b,c,d,e){var f=ea(c)?pb:J.current;f=Nb(b,f);Sb(b,e);c=mf(a,b,c,d,f,e);d=nf();if(null!==a&&!ha)return b.updateQueue=a.updateQueue,b.flags&=-2053,a.lanes&=~e,Qa(a,b,e);D&&d&&Ue(b);b.flags|=1;aa(a,b,c,e);return b.child}function ri(a,b,c,d,e){if(ea(c)){var f=!0;ld(b)}else f=!1;Sb(b,e);if(null===b.stateNode)Fd(a,b),ei(b,c,d),uf(b,c,d,e),d=!0;else if(null===a){var g=b.stateNode,h=b.memoizedProps;g.props=h;var k=g.context,n=c.contextType;"object"===typeof n&&null!==n?n=qa(n):(n=ea(c)?pb:J.current,n=Nb(b,
|
||||
n));var l=c.getDerivedStateFromProps,m="function"===typeof l||"function"===typeof g.getSnapshotBeforeUpdate;m||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==d||k!==n)&&fi(b,g,d,n);gb=!1;var r=b.memoizedState;g.state=r;wd(b,d,g,e);k=b.memoizedState;h!==d||r!==k||S.current||gb?("function"===typeof l&&(tf(b,c,l,d),k=b.memoizedState),(h=gb||di(b,c,h,d,r,k,n))?(m||"function"!==typeof g.UNSAFE_componentWillMount&&"function"!==typeof g.componentWillMount||
|
||||
("function"===typeof g.componentWillMount&&g.componentWillMount(),"function"===typeof g.UNSAFE_componentWillMount&&g.UNSAFE_componentWillMount()),"function"===typeof g.componentDidMount&&(b.flags|=4194308)):("function"===typeof g.componentDidMount&&(b.flags|=4194308),b.memoizedProps=d,b.memoizedState=k),g.props=d,g.state=k,g.context=n,d=h):("function"===typeof g.componentDidMount&&(b.flags|=4194308),d=!1)}else{g=b.stateNode;Fh(a,b);h=b.memoizedProps;n=b.type===b.elementType?h:ya(b.type,h);g.props=
|
||||
n;m=b.pendingProps;r=g.context;k=c.contextType;"object"===typeof k&&null!==k?k=qa(k):(k=ea(c)?pb:J.current,k=Nb(b,k));var p=c.getDerivedStateFromProps;(l="function"===typeof p||"function"===typeof g.getSnapshotBeforeUpdate)||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==m||r!==k)&&fi(b,g,d,k);gb=!1;r=b.memoizedState;g.state=r;wd(b,d,g,e);var x=b.memoizedState;h!==m||r!==x||S.current||gb?("function"===typeof p&&(tf(b,c,p,d),x=b.memoizedState),
|
||||
(n=gb||di(b,c,n,d,r,x,k)||!1)?(l||"function"!==typeof g.UNSAFE_componentWillUpdate&&"function"!==typeof g.componentWillUpdate||("function"===typeof g.componentWillUpdate&&g.componentWillUpdate(d,x,k),"function"===typeof g.UNSAFE_componentWillUpdate&&g.UNSAFE_componentWillUpdate(d,x,k)),"function"===typeof g.componentDidUpdate&&(b.flags|=4),"function"===typeof g.getSnapshotBeforeUpdate&&(b.flags|=1024)):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=
|
||||
4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=1024),b.memoizedProps=d,b.memoizedState=x),g.props=d,g.state=x,g.context=k,d=n):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=1024),d=!1)}return Af(a,b,c,d,f,e)}function Af(a,b,c,d,e,f){qi(a,b);var g=0!==(b.flags&128);if(!d&&!g)return e&&vh(b,c,!1),
|
||||
Qa(a,b,f);d=b.stateNode;tk.current=b;var h=g&&"function"!==typeof c.getDerivedStateFromError?null:d.render();b.flags|=1;null!==a&&g?(b.child=Vb(b,a.child,null,f),b.child=Vb(b,null,h,f)):aa(a,b,h,f);b.memoizedState=d.state;e&&vh(b,c,!0);return b.child}function si(a){var b=a.stateNode;b.pendingContext?th(a,b.pendingContext,b.pendingContext!==b.context):b.context&&th(a,b.context,!1);gf(a,b.containerInfo)}function ti(a,b,c,d,e){Qb();Ye(e);b.flags|=256;aa(a,b,c,d);return b.child}function Bf(a){return{baseLanes:a,
|
||||
cachePool:null,transitions:null}}function ui(a,b,c){var d=b.pendingProps,e=F.current,f=!1,g=0!==(b.flags&128),h;(h=g)||(h=null!==a&&null===a.memoizedState?!1:0!==(e&2));if(h)f=!0,b.flags&=-129;else if(null===a||null!==a.memoizedState)e|=1;y(F,e&1);if(null===a){Xe(b);a=b.memoizedState;if(null!==a&&(a=a.dehydrated,null!==a))return 0===(b.mode&1)?b.lanes=1:"$!"===a.data?b.lanes=8:b.lanes=1073741824,null;g=d.children;a=d.fallback;return f?(d=b.mode,f=b.child,g={mode:"hidden",children:g},0===(d&1)&&null!==
|
||||
f?(f.childLanes=0,f.pendingProps=g):f=Gd(g,d,0,null),a=sb(a,d,c,null),f.return=b,a.return=b,f.sibling=a,b.child=f,b.child.memoizedState=Bf(c),b.memoizedState=Cf,a):Df(b,g)}e=a.memoizedState;if(null!==e&&(h=e.dehydrated,null!==h))return uk(a,b,g,d,h,e,c);if(f){f=d.fallback;g=b.mode;e=a.child;h=e.sibling;var k={mode:"hidden",children:d.children};0===(g&1)&&b.child!==e?(d=b.child,d.childLanes=0,d.pendingProps=k,b.deletions=null):(d=eb(e,k),d.subtreeFlags=e.subtreeFlags&14680064);null!==h?f=eb(h,f):(f=
|
||||
sb(f,g,c,null),f.flags|=2);f.return=b;d.return=b;d.sibling=f;b.child=d;d=f;f=b.child;g=a.child.memoizedState;g=null===g?Bf(c):{baseLanes:g.baseLanes|c,cachePool:null,transitions:g.transitions};f.memoizedState=g;f.childLanes=a.childLanes&~c;b.memoizedState=Cf;return d}f=a.child;a=f.sibling;d=eb(f,{mode:"visible",children:d.children});0===(b.mode&1)&&(d.lanes=c);d.return=b;d.sibling=null;null!==a&&(c=b.deletions,null===c?(b.deletions=[a],b.flags|=16):c.push(a));b.child=d;b.memoizedState=null;return d}
|
||||
function Df(a,b,c){b=Gd({mode:"visible",children:b},a.mode,0,null);b.return=a;return a.child=b}function Hd(a,b,c,d){null!==d&&Ye(d);Vb(b,a.child,null,c);a=Df(b,b.pendingProps.children);a.flags|=2;b.memoizedState=null;return a}function uk(a,b,c,d,e,f,g){if(c){if(b.flags&256)return b.flags&=-257,d=vf(Error(m(422))),Hd(a,b,g,d);if(null!==b.memoizedState)return b.child=a.child,b.flags|=128,null;f=d.fallback;e=b.mode;d=Gd({mode:"visible",children:d.children},e,0,null);f=sb(f,e,g,null);f.flags|=2;d.return=
|
||||
b;f.return=b;d.sibling=f;b.child=d;0!==(b.mode&1)&&Vb(b,a.child,null,g);b.child.memoizedState=Bf(g);b.memoizedState=Cf;return f}if(0===(b.mode&1))return Hd(a,b,g,null);if("$!"===e.data){d=e.nextSibling&&e.nextSibling.dataset;if(d)var h=d.dgst;d=h;f=Error(m(419));d=vf(f,d,void 0);return Hd(a,b,g,d)}h=0!==(g&a.childLanes);if(ha||h){d=O;if(null!==d){switch(g&-g){case 4:e=2;break;case 16:e=8;break;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:e=
|
||||
32;break;case 536870912:e=268435456;break;default:e=0}e=0!==(e&(d.suspendedLanes|g))?0:e;0!==e&&e!==f.retryLane&&(f.retryLane=e,Oa(a,e),xa(d,a,e,-1))}Ef();d=vf(Error(m(421)));return Hd(a,b,g,d)}if("$?"===e.data)return b.flags|=128,b.child=a.child,b=vk.bind(null,a),e._reactRetry=b,null;a=f.treeContext;fa=Ka(e.nextSibling);la=b;D=!0;wa=null;null!==a&&(na[oa++]=Ma,na[oa++]=Na,na[oa++]=rb,Ma=a.id,Na=a.overflow,rb=b);b=Df(b,d.children);b.flags|=4096;return b}function vi(a,b,c){a.lanes|=b;var d=a.alternate;
|
||||
null!==d&&(d.lanes|=b);df(a.return,b,c)}function Ff(a,b,c,d,e){var f=a.memoizedState;null===f?a.memoizedState={isBackwards:b,rendering:null,renderingStartTime:0,last:d,tail:c,tailMode:e}:(f.isBackwards=b,f.rendering=null,f.renderingStartTime=0,f.last=d,f.tail=c,f.tailMode=e)}function wi(a,b,c){var d=b.pendingProps,e=d.revealOrder,f=d.tail;aa(a,b,d.children,c);d=F.current;if(0!==(d&2))d=d&1|2,b.flags|=128;else{if(null!==a&&0!==(a.flags&128))a:for(a=b.child;null!==a;){if(13===a.tag)null!==a.memoizedState&&
|
||||
vi(a,c,b);else if(19===a.tag)vi(a,c,b);else if(null!==a.child){a.child.return=a;a=a.child;continue}if(a===b)break a;for(;null===a.sibling;){if(null===a.return||a.return===b)break a;a=a.return}a.sibling.return=a.return;a=a.sibling}d&=1}y(F,d);if(0===(b.mode&1))b.memoizedState=null;else switch(e){case "forwards":c=b.child;for(e=null;null!==c;)a=c.alternate,null!==a&&null===xd(a)&&(e=c),c=c.sibling;c=e;null===c?(e=b.child,b.child=null):(e=c.sibling,c.sibling=null);Ff(b,!1,e,c,f);break;case "backwards":c=
|
||||
null;e=b.child;for(b.child=null;null!==e;){a=e.alternate;if(null!==a&&null===xd(a)){b.child=e;break}a=e.sibling;e.sibling=c;c=e;e=a}Ff(b,!0,c,null,f);break;case "together":Ff(b,!1,null,null,void 0);break;default:b.memoizedState=null}return b.child}function Fd(a,b){0===(b.mode&1)&&null!==a&&(a.alternate=null,b.alternate=null,b.flags|=2)}function Qa(a,b,c){null!==a&&(b.dependencies=a.dependencies);ra|=b.lanes;if(0===(c&b.childLanes))return null;if(null!==a&&b.child!==a.child)throw Error(m(153));if(null!==
|
||||
b.child){a=b.child;c=eb(a,a.pendingProps);b.child=c;for(c.return=b;null!==a.sibling;)a=a.sibling,c=c.sibling=eb(a,a.pendingProps),c.return=b;c.sibling=null}return b.child}function wk(a,b,c){switch(b.tag){case 3:si(b);Qb();break;case 5:Ih(b);break;case 1:ea(b.type)&&ld(b);break;case 4:gf(b,b.stateNode.containerInfo);break;case 10:var d=b.type._context,e=b.memoizedProps.value;y(ud,d._currentValue);d._currentValue=e;break;case 13:d=b.memoizedState;if(null!==d){if(null!==d.dehydrated)return y(F,F.current&
|
||||
1),b.flags|=128,null;if(0!==(c&b.child.childLanes))return ui(a,b,c);y(F,F.current&1);a=Qa(a,b,c);return null!==a?a.sibling:null}y(F,F.current&1);break;case 19:d=0!==(c&b.childLanes);if(0!==(a.flags&128)){if(d)return wi(a,b,c);b.flags|=128}e=b.memoizedState;null!==e&&(e.rendering=null,e.tail=null,e.lastEffect=null);y(F,F.current);if(d)break;else return null;case 22:case 23:return b.lanes=0,pi(a,b,c)}return Qa(a,b,c)}function Dc(a,b){if(!D)switch(a.tailMode){case "hidden":b=a.tail;for(var c=null;null!==
|
||||
b;)null!==b.alternate&&(c=b),b=b.sibling;null===c?a.tail=null:c.sibling=null;break;case "collapsed":c=a.tail;for(var d=null;null!==c;)null!==c.alternate&&(d=c),c=c.sibling;null===d?b||null===a.tail?a.tail=null:a.tail.sibling=null:d.sibling=null}}function W(a){var b=null!==a.alternate&&a.alternate.child===a.child,c=0,d=0;if(b)for(var e=a.child;null!==e;)c|=e.lanes|e.childLanes,d|=e.subtreeFlags&14680064,d|=e.flags&14680064,e.return=a,e=e.sibling;else for(e=a.child;null!==e;)c|=e.lanes|e.childLanes,
|
||||
d|=e.subtreeFlags,d|=e.flags,e.return=a,e=e.sibling;a.subtreeFlags|=d;a.childLanes=c;return b}function xk(a,b,c){var d=b.pendingProps;Ve(b);switch(b.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return W(b),null;case 1:return ea(b.type)&&(v(S),v(J)),W(b),null;case 3:d=b.stateNode;Tb();v(S);v(J);jf();d.pendingContext&&(d.context=d.pendingContext,d.pendingContext=null);if(null===a||null===a.child)pd(b)?b.flags|=4:null===a||a.memoizedState.isDehydrated&&0===(b.flags&
|
||||
256)||(b.flags|=1024,null!==wa&&(Gf(wa),wa=null));xi(a,b);W(b);return null;case 5:hf(b);var e=ub(xc.current);c=b.type;if(null!==a&&null!=b.stateNode)yk(a,b,c,d,e),a.ref!==b.ref&&(b.flags|=512,b.flags|=2097152);else{if(!d){if(null===b.stateNode)throw Error(m(166));W(b);return null}a=ub(Ea.current);if(pd(b)){d=b.stateNode;c=b.type;var f=b.memoizedProps;d[Da]=b;d[uc]=f;a=0!==(b.mode&1);switch(c){case "dialog":B("cancel",d);B("close",d);break;case "iframe":case "object":case "embed":B("load",d);break;
|
||||
case "video":case "audio":for(e=0;e<Ec.length;e++)B(Ec[e],d);break;case "source":B("error",d);break;case "img":case "image":case "link":B("error",d);B("load",d);break;case "details":B("toggle",d);break;case "input":kg(d,f);B("invalid",d);break;case "select":d._wrapperState={wasMultiple:!!f.multiple};B("invalid",d);break;case "textarea":ng(d,f),B("invalid",d)}pe(c,f);e=null;for(var g in f)if(f.hasOwnProperty(g)){var h=f[g];"children"===g?"string"===typeof h?d.textContent!==h&&(!0!==f.suppressHydrationWarning&&
|
||||
jd(d.textContent,h,a),e=["children",h]):"number"===typeof h&&d.textContent!==""+h&&(!0!==f.suppressHydrationWarning&&jd(d.textContent,h,a),e=["children",""+h]):$b.hasOwnProperty(g)&&null!=h&&"onScroll"===g&&B("scroll",d)}switch(c){case "input":Pc(d);mg(d,f,!0);break;case "textarea":Pc(d);pg(d);break;case "select":case "option":break;default:"function"===typeof f.onClick&&(d.onclick=kd)}d=e;b.updateQueue=d;null!==d&&(b.flags|=4)}else{g=9===e.nodeType?e:e.ownerDocument;"http://www.w3.org/1999/xhtml"===
|
||||
a&&(a=qg(c));"http://www.w3.org/1999/xhtml"===a?"script"===c?(a=g.createElement("div"),a.innerHTML="<script>\x3c/script>",a=a.removeChild(a.firstChild)):"string"===typeof d.is?a=g.createElement(c,{is:d.is}):(a=g.createElement(c),"select"===c&&(g=a,d.multiple?g.multiple=!0:d.size&&(g.size=d.size))):a=g.createElementNS(a,c);a[Da]=b;a[uc]=d;zk(a,b,!1,!1);b.stateNode=a;a:{g=qe(c,d);switch(c){case "dialog":B("cancel",a);B("close",a);e=d;break;case "iframe":case "object":case "embed":B("load",a);e=d;break;
|
||||
case "video":case "audio":for(e=0;e<Ec.length;e++)B(Ec[e],a);e=d;break;case "source":B("error",a);e=d;break;case "img":case "image":case "link":B("error",a);B("load",a);e=d;break;case "details":B("toggle",a);e=d;break;case "input":kg(a,d);e=ke(a,d);B("invalid",a);break;case "option":e=d;break;case "select":a._wrapperState={wasMultiple:!!d.multiple};e=E({},d,{value:void 0});B("invalid",a);break;case "textarea":ng(a,d);e=ne(a,d);B("invalid",a);break;default:e=d}pe(c,e);h=e;for(f in h)if(h.hasOwnProperty(f)){var k=
|
||||
h[f];"style"===f?sg(a,k):"dangerouslySetInnerHTML"===f?(k=k?k.__html:void 0,null!=k&&yi(a,k)):"children"===f?"string"===typeof k?("textarea"!==c||""!==k)&&Fc(a,k):"number"===typeof k&&Fc(a,""+k):"suppressContentEditableWarning"!==f&&"suppressHydrationWarning"!==f&&"autoFocus"!==f&&($b.hasOwnProperty(f)?null!=k&&"onScroll"===f&&B("scroll",a):null!=k&&$d(a,f,k,g))}switch(c){case "input":Pc(a);mg(a,d,!1);break;case "textarea":Pc(a);pg(a);break;case "option":null!=d.value&&a.setAttribute("value",""+Ua(d.value));
|
||||
break;case "select":a.multiple=!!d.multiple;f=d.value;null!=f?Db(a,!!d.multiple,f,!1):null!=d.defaultValue&&Db(a,!!d.multiple,d.defaultValue,!0);break;default:"function"===typeof e.onClick&&(a.onclick=kd)}switch(c){case "button":case "input":case "select":case "textarea":d=!!d.autoFocus;break a;case "img":d=!0;break a;default:d=!1}}d&&(b.flags|=4)}null!==b.ref&&(b.flags|=512,b.flags|=2097152)}W(b);return null;case 6:if(a&&null!=b.stateNode)Ak(a,b,a.memoizedProps,d);else{if("string"!==typeof d&&null===
|
||||
b.stateNode)throw Error(m(166));c=ub(xc.current);ub(Ea.current);if(pd(b)){d=b.stateNode;c=b.memoizedProps;d[Da]=b;if(f=d.nodeValue!==c)if(a=la,null!==a)switch(a.tag){case 3:jd(d.nodeValue,c,0!==(a.mode&1));break;case 5:!0!==a.memoizedProps.suppressHydrationWarning&&jd(d.nodeValue,c,0!==(a.mode&1))}f&&(b.flags|=4)}else d=(9===c.nodeType?c:c.ownerDocument).createTextNode(d),d[Da]=b,b.stateNode=d}W(b);return null;case 13:v(F);d=b.memoizedState;if(null===a||null!==a.memoizedState&&null!==a.memoizedState.dehydrated){if(D&&
|
||||
null!==fa&&0!==(b.mode&1)&&0===(b.flags&128)){for(f=fa;f;)f=Ka(f.nextSibling);Qb();b.flags|=98560;f=!1}else if(f=pd(b),null!==d&&null!==d.dehydrated){if(null===a){if(!f)throw Error(m(318));f=b.memoizedState;f=null!==f?f.dehydrated:null;if(!f)throw Error(m(317));f[Da]=b}else Qb(),0===(b.flags&128)&&(b.memoizedState=null),b.flags|=4;W(b);f=!1}else null!==wa&&(Gf(wa),wa=null),f=!0;if(!f)return b.flags&65536?b:null}if(0!==(b.flags&128))return b.lanes=c,b;d=null!==d;d!==(null!==a&&null!==a.memoizedState)&&
|
||||
d&&(b.child.flags|=8192,0!==(b.mode&1)&&(null===a||0!==(F.current&1)?0===L&&(L=3):Ef()));null!==b.updateQueue&&(b.flags|=4);W(b);return null;case 4:return Tb(),xi(a,b),null===a&&sc(b.stateNode.containerInfo),W(b),null;case 10:return cf(b.type._context),W(b),null;case 17:return ea(b.type)&&(v(S),v(J)),W(b),null;case 19:v(F);f=b.memoizedState;if(null===f)return W(b),null;d=0!==(b.flags&128);g=f.rendering;if(null===g)if(d)Dc(f,!1);else{if(0!==L||null!==a&&0!==(a.flags&128))for(a=b.child;null!==a;){g=
|
||||
xd(a);if(null!==g){b.flags|=128;Dc(f,!1);d=g.updateQueue;null!==d&&(b.updateQueue=d,b.flags|=4);b.subtreeFlags=0;d=c;for(c=b.child;null!==c;)f=c,a=d,f.flags&=14680066,g=f.alternate,null===g?(f.childLanes=0,f.lanes=a,f.child=null,f.subtreeFlags=0,f.memoizedProps=null,f.memoizedState=null,f.updateQueue=null,f.dependencies=null,f.stateNode=null):(f.childLanes=g.childLanes,f.lanes=g.lanes,f.child=g.child,f.subtreeFlags=0,f.deletions=null,f.memoizedProps=g.memoizedProps,f.memoizedState=g.memoizedState,
|
||||
f.updateQueue=g.updateQueue,f.type=g.type,a=g.dependencies,f.dependencies=null===a?null:{lanes:a.lanes,firstContext:a.firstContext}),c=c.sibling;y(F,F.current&1|2);return b.child}a=a.sibling}null!==f.tail&&P()>Hf&&(b.flags|=128,d=!0,Dc(f,!1),b.lanes=4194304)}else{if(!d)if(a=xd(g),null!==a){if(b.flags|=128,d=!0,c=a.updateQueue,null!==c&&(b.updateQueue=c,b.flags|=4),Dc(f,!0),null===f.tail&&"hidden"===f.tailMode&&!g.alternate&&!D)return W(b),null}else 2*P()-f.renderingStartTime>Hf&&1073741824!==c&&(b.flags|=
|
||||
128,d=!0,Dc(f,!1),b.lanes=4194304);f.isBackwards?(g.sibling=b.child,b.child=g):(c=f.last,null!==c?c.sibling=g:b.child=g,f.last=g)}if(null!==f.tail)return b=f.tail,f.rendering=b,f.tail=b.sibling,f.renderingStartTime=P(),b.sibling=null,c=F.current,y(F,d?c&1|2:c&1),b;W(b);return null;case 22:case 23:return ba=Ga.current,v(Ga),d=null!==b.memoizedState,null!==a&&null!==a.memoizedState!==d&&(b.flags|=8192),d&&0!==(b.mode&1)?0!==(ba&1073741824)&&(W(b),b.subtreeFlags&6&&(b.flags|=8192)):W(b),null;case 24:return null;
|
||||
case 25:return null}throw Error(m(156,b.tag));}function Bk(a,b,c){Ve(b);switch(b.tag){case 1:return ea(b.type)&&(v(S),v(J)),a=b.flags,a&65536?(b.flags=a&-65537|128,b):null;case 3:return Tb(),v(S),v(J),jf(),a=b.flags,0!==(a&65536)&&0===(a&128)?(b.flags=a&-65537|128,b):null;case 5:return hf(b),null;case 13:v(F);a=b.memoizedState;if(null!==a&&null!==a.dehydrated){if(null===b.alternate)throw Error(m(340));Qb()}a=b.flags;return a&65536?(b.flags=a&-65537|128,b):null;case 19:return v(F),null;case 4:return Tb(),
|
||||
null;case 10:return cf(b.type._context),null;case 22:case 23:return ba=Ga.current,v(Ga),null;case 24:return null;default:return null}}function Wb(a,b){var c=a.ref;if(null!==c)if("function"===typeof c)try{c(null)}catch(d){G(a,b,d)}else c.current=null}function If(a,b,c){try{c()}catch(d){G(a,b,d)}}function Ck(a,b){Jf=Zc;a=ch();if(Ie(a)){if("selectionStart"in a)var c={start:a.selectionStart,end:a.selectionEnd};else a:{c=(c=a.ownerDocument)&&c.defaultView||window;var d=c.getSelection&&c.getSelection();
|
||||
if(d&&0!==d.rangeCount){c=d.anchorNode;var e=d.anchorOffset,f=d.focusNode;d=d.focusOffset;try{c.nodeType,f.nodeType}catch(M){c=null;break a}var g=0,h=-1,k=-1,n=0,q=0,u=a,r=null;b:for(;;){for(var p;;){u!==c||0!==e&&3!==u.nodeType||(h=g+e);u!==f||0!==d&&3!==u.nodeType||(k=g+d);3===u.nodeType&&(g+=u.nodeValue.length);if(null===(p=u.firstChild))break;r=u;u=p}for(;;){if(u===a)break b;r===c&&++n===e&&(h=g);r===f&&++q===d&&(k=g);if(null!==(p=u.nextSibling))break;u=r;r=u.parentNode}u=p}c=-1===h||-1===k?null:
|
||||
{start:h,end:k}}else c=null}c=c||{start:0,end:0}}else c=null;Kf={focusedElem:a,selectionRange:c};Zc=!1;for(l=b;null!==l;)if(b=l,a=b.child,0!==(b.subtreeFlags&1028)&&null!==a)a.return=b,l=a;else for(;null!==l;){b=l;try{var x=b.alternate;if(0!==(b.flags&1024))switch(b.tag){case 0:case 11:case 15:break;case 1:if(null!==x){var v=x.memoizedProps,z=x.memoizedState,w=b.stateNode,A=w.getSnapshotBeforeUpdate(b.elementType===b.type?v:ya(b.type,v),z);w.__reactInternalSnapshotBeforeUpdate=A}break;case 3:var t=
|
||||
b.stateNode.containerInfo;1===t.nodeType?t.textContent="":9===t.nodeType&&t.documentElement&&t.removeChild(t.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(m(163));}}catch(M){G(b,b.return,M)}a=b.sibling;if(null!==a){a.return=b.return;l=a;break}l=b.return}x=zi;zi=!1;return x}function Gc(a,b,c){var d=b.updateQueue;d=null!==d?d.lastEffect:null;if(null!==d){var e=d=d.next;do{if((e.tag&a)===a){var f=e.destroy;e.destroy=void 0;void 0!==f&&If(b,c,f)}e=e.next}while(e!==d)}}
|
||||
function Id(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.create;c.destroy=d()}c=c.next}while(c!==b)}}function Lf(a){var b=a.ref;if(null!==b){var c=a.stateNode;switch(a.tag){case 5:a=c;break;default:a=c}"function"===typeof b?b(a):b.current=a}}function Ai(a){var b=a.alternate;null!==b&&(a.alternate=null,Ai(b));a.child=null;a.deletions=null;a.sibling=null;5===a.tag&&(b=a.stateNode,null!==b&&(delete b[Da],delete b[uc],delete b[Me],delete b[Dk],
|
||||
delete b[Ek]));a.stateNode=null;a.return=null;a.dependencies=null;a.memoizedProps=null;a.memoizedState=null;a.pendingProps=null;a.stateNode=null;a.updateQueue=null}function Bi(a){return 5===a.tag||3===a.tag||4===a.tag}function Ci(a){a:for(;;){for(;null===a.sibling;){if(null===a.return||Bi(a.return))return null;a=a.return}a.sibling.return=a.return;for(a=a.sibling;5!==a.tag&&6!==a.tag&&18!==a.tag;){if(a.flags&2)continue a;if(null===a.child||4===a.tag)continue a;else a.child.return=a,a=a.child}if(!(a.flags&
|
||||
2))return a.stateNode}}function Mf(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?8===c.nodeType?c.parentNode.insertBefore(a,b):c.insertBefore(a,b):(8===c.nodeType?(b=c.parentNode,b.insertBefore(a,c)):(b=c,b.appendChild(a)),c=c._reactRootContainer,null!==c&&void 0!==c||null!==b.onclick||(b.onclick=kd));else if(4!==d&&(a=a.child,null!==a))for(Mf(a,b,c),a=a.sibling;null!==a;)Mf(a,b,c),a=a.sibling}function Nf(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?c.insertBefore(a,b):c.appendChild(a);
|
||||
else if(4!==d&&(a=a.child,null!==a))for(Nf(a,b,c),a=a.sibling;null!==a;)Nf(a,b,c),a=a.sibling}function jb(a,b,c){for(c=c.child;null!==c;)Di(a,b,c),c=c.sibling}function Di(a,b,c){if(Ca&&"function"===typeof Ca.onCommitFiberUnmount)try{Ca.onCommitFiberUnmount(Uc,c)}catch(h){}switch(c.tag){case 5:X||Wb(c,b);case 6:var d=T,e=za;T=null;jb(a,b,c);T=d;za=e;null!==T&&(za?(a=T,c=c.stateNode,8===a.nodeType?a.parentNode.removeChild(c):a.removeChild(c)):T.removeChild(c.stateNode));break;case 18:null!==T&&(za?
|
||||
(a=T,c=c.stateNode,8===a.nodeType?Re(a.parentNode,c):1===a.nodeType&&Re(a,c),nc(a)):Re(T,c.stateNode));break;case 4:d=T;e=za;T=c.stateNode.containerInfo;za=!0;jb(a,b,c);T=d;za=e;break;case 0:case 11:case 14:case 15:if(!X&&(d=c.updateQueue,null!==d&&(d=d.lastEffect,null!==d))){e=d=d.next;do{var f=e,g=f.destroy;f=f.tag;void 0!==g&&(0!==(f&2)?If(c,b,g):0!==(f&4)&&If(c,b,g));e=e.next}while(e!==d)}jb(a,b,c);break;case 1:if(!X&&(Wb(c,b),d=c.stateNode,"function"===typeof d.componentWillUnmount))try{d.props=
|
||||
c.memoizedProps,d.state=c.memoizedState,d.componentWillUnmount()}catch(h){G(c,b,h)}jb(a,b,c);break;case 21:jb(a,b,c);break;case 22:c.mode&1?(X=(d=X)||null!==c.memoizedState,jb(a,b,c),X=d):jb(a,b,c);break;default:jb(a,b,c)}}function Ei(a){var b=a.updateQueue;if(null!==b){a.updateQueue=null;var c=a.stateNode;null===c&&(c=a.stateNode=new Fk);b.forEach(function(b){var d=Gk.bind(null,a,b);c.has(b)||(c.add(b),b.then(d,d))})}}function Aa(a,b,c){c=b.deletions;if(null!==c)for(var d=0;d<c.length;d++){var e=
|
||||
c[d];try{var f=a,g=b,h=g;a:for(;null!==h;){switch(h.tag){case 5:T=h.stateNode;za=!1;break a;case 3:T=h.stateNode.containerInfo;za=!0;break a;case 4:T=h.stateNode.containerInfo;za=!0;break a}h=h.return}if(null===T)throw Error(m(160));Di(f,g,e);T=null;za=!1;var k=e.alternate;null!==k&&(k.return=null);e.return=null}catch(n){G(e,b,n)}}if(b.subtreeFlags&12854)for(b=b.child;null!==b;)Fi(b,a),b=b.sibling}function Fi(a,b,c){var d=a.alternate;c=a.flags;switch(a.tag){case 0:case 11:case 14:case 15:Aa(b,a);
|
||||
Ha(a);if(c&4){try{Gc(3,a,a.return),Id(3,a)}catch(I){G(a,a.return,I)}try{Gc(5,a,a.return)}catch(I){G(a,a.return,I)}}break;case 1:Aa(b,a);Ha(a);c&512&&null!==d&&Wb(d,d.return);break;case 5:Aa(b,a);Ha(a);c&512&&null!==d&&Wb(d,d.return);if(a.flags&32){var e=a.stateNode;try{Fc(e,"")}catch(I){G(a,a.return,I)}}if(c&4&&(e=a.stateNode,null!=e)){var f=a.memoizedProps,g=null!==d?d.memoizedProps:f,h=a.type,k=a.updateQueue;a.updateQueue=null;if(null!==k)try{"input"===h&&"radio"===f.type&&null!=f.name&&lg(e,f);
|
||||
qe(h,g);var n=qe(h,f);for(g=0;g<k.length;g+=2){var q=k[g],u=k[g+1];"style"===q?sg(e,u):"dangerouslySetInnerHTML"===q?yi(e,u):"children"===q?Fc(e,u):$d(e,q,u,n)}switch(h){case "input":le(e,f);break;case "textarea":og(e,f);break;case "select":var r=e._wrapperState.wasMultiple;e._wrapperState.wasMultiple=!!f.multiple;var p=f.value;null!=p?Db(e,!!f.multiple,p,!1):r!==!!f.multiple&&(null!=f.defaultValue?Db(e,!!f.multiple,f.defaultValue,!0):Db(e,!!f.multiple,f.multiple?[]:"",!1))}e[uc]=f}catch(I){G(a,a.return,
|
||||
I)}}break;case 6:Aa(b,a);Ha(a);if(c&4){if(null===a.stateNode)throw Error(m(162));e=a.stateNode;f=a.memoizedProps;try{e.nodeValue=f}catch(I){G(a,a.return,I)}}break;case 3:Aa(b,a);Ha(a);if(c&4&&null!==d&&d.memoizedState.isDehydrated)try{nc(b.containerInfo)}catch(I){G(a,a.return,I)}break;case 4:Aa(b,a);Ha(a);break;case 13:Aa(b,a);Ha(a);e=a.child;e.flags&8192&&(f=null!==e.memoizedState,e.stateNode.isHidden=f,!f||null!==e.alternate&&null!==e.alternate.memoizedState||(Of=P()));c&4&&Ei(a);break;case 22:q=
|
||||
null!==d&&null!==d.memoizedState;a.mode&1?(X=(n=X)||q,Aa(b,a),X=n):Aa(b,a);Ha(a);if(c&8192){n=null!==a.memoizedState;if((a.stateNode.isHidden=n)&&!q&&0!==(a.mode&1))for(l=a,q=a.child;null!==q;){for(u=l=q;null!==l;){r=l;p=r.child;switch(r.tag){case 0:case 11:case 14:case 15:Gc(4,r,r.return);break;case 1:Wb(r,r.return);var x=r.stateNode;if("function"===typeof x.componentWillUnmount){c=r;b=r.return;try{d=c,x.props=d.memoizedProps,x.state=d.memoizedState,x.componentWillUnmount()}catch(I){G(c,b,I)}}break;
|
||||
case 5:Wb(r,r.return);break;case 22:if(null!==r.memoizedState){Gi(u);continue}}null!==p?(p.return=r,l=p):Gi(u)}q=q.sibling}a:for(q=null,u=a;;){if(5===u.tag){if(null===q){q=u;try{e=u.stateNode,n?(f=e.style,"function"===typeof f.setProperty?f.setProperty("display","none","important"):f.display="none"):(h=u.stateNode,k=u.memoizedProps.style,g=void 0!==k&&null!==k&&k.hasOwnProperty("display")?k.display:null,h.style.display=rg("display",g))}catch(I){G(a,a.return,I)}}}else if(6===u.tag){if(null===q)try{u.stateNode.nodeValue=
|
||||
n?"":u.memoizedProps}catch(I){G(a,a.return,I)}}else if((22!==u.tag&&23!==u.tag||null===u.memoizedState||u===a)&&null!==u.child){u.child.return=u;u=u.child;continue}if(u===a)break a;for(;null===u.sibling;){if(null===u.return||u.return===a)break a;q===u&&(q=null);u=u.return}q===u&&(q=null);u.sibling.return=u.return;u=u.sibling}}break;case 19:Aa(b,a);Ha(a);c&4&&Ei(a);break;case 21:break;default:Aa(b,a),Ha(a)}}function Ha(a){var b=a.flags;if(b&2){try{a:{for(var c=a.return;null!==c;){if(Bi(c)){var d=c;
|
||||
break a}c=c.return}throw Error(m(160));}switch(d.tag){case 5:var e=d.stateNode;d.flags&32&&(Fc(e,""),d.flags&=-33);var f=Ci(a);Nf(a,f,e);break;case 3:case 4:var g=d.stateNode.containerInfo,h=Ci(a);Mf(a,h,g);break;default:throw Error(m(161));}}catch(k){G(a,a.return,k)}a.flags&=-3}b&4096&&(a.flags&=-4097)}function Hk(a,b,c){l=a;Hi(a,b,c)}function Hi(a,b,c){for(var d=0!==(a.mode&1);null!==l;){var e=l,f=e.child;if(22===e.tag&&d){var g=null!==e.memoizedState||Jd;if(!g){var h=e.alternate,k=null!==h&&null!==
|
||||
h.memoizedState||X;h=Jd;var n=X;Jd=g;if((X=k)&&!n)for(l=e;null!==l;)g=l,k=g.child,22===g.tag&&null!==g.memoizedState?Ii(e):null!==k?(k.return=g,l=k):Ii(e);for(;null!==f;)l=f,Hi(f,b,c),f=f.sibling;l=e;Jd=h;X=n}Ji(a,b,c)}else 0!==(e.subtreeFlags&8772)&&null!==f?(f.return=e,l=f):Ji(a,b,c)}}function Ji(a,b,c){for(;null!==l;){b=l;if(0!==(b.flags&8772)){c=b.alternate;try{if(0!==(b.flags&8772))switch(b.tag){case 0:case 11:case 15:X||Id(5,b);break;case 1:var d=b.stateNode;if(b.flags&4&&!X)if(null===c)d.componentDidMount();
|
||||
else{var e=b.elementType===b.type?c.memoizedProps:ya(b.type,c.memoizedProps);d.componentDidUpdate(e,c.memoizedState,d.__reactInternalSnapshotBeforeUpdate)}var f=b.updateQueue;null!==f&&Hh(b,f,d);break;case 3:var g=b.updateQueue;if(null!==g){c=null;if(null!==b.child)switch(b.child.tag){case 5:c=b.child.stateNode;break;case 1:c=b.child.stateNode}Hh(b,g,c)}break;case 5:var h=b.stateNode;if(null===c&&b.flags&4){c=h;var k=b.memoizedProps;switch(b.type){case "button":case "input":case "select":case "textarea":k.autoFocus&&
|
||||
c.focus();break;case "img":k.src&&(c.src=k.src)}}break;case 6:break;case 4:break;case 12:break;case 13:if(null===b.memoizedState){var n=b.alternate;if(null!==n){var q=n.memoizedState;if(null!==q){var p=q.dehydrated;null!==p&&nc(p)}}}break;case 19:case 17:case 21:case 22:case 23:case 25:break;default:throw Error(m(163));}X||b.flags&512&&Lf(b)}catch(r){G(b,b.return,r)}}if(b===a){l=null;break}c=b.sibling;if(null!==c){c.return=b.return;l=c;break}l=b.return}}function Gi(a){for(;null!==l;){var b=l;if(b===
|
||||
a){l=null;break}var c=b.sibling;if(null!==c){c.return=b.return;l=c;break}l=b.return}}function Ii(a){for(;null!==l;){var b=l;try{switch(b.tag){case 0:case 11:case 15:var c=b.return;try{Id(4,b)}catch(k){G(b,c,k)}break;case 1:var d=b.stateNode;if("function"===typeof d.componentDidMount){var e=b.return;try{d.componentDidMount()}catch(k){G(b,e,k)}}var f=b.return;try{Lf(b)}catch(k){G(b,f,k)}break;case 5:var g=b.return;try{Lf(b)}catch(k){G(b,g,k)}}}catch(k){G(b,b.return,k)}if(b===a){l=null;break}var h=b.sibling;
|
||||
if(null!==h){h.return=b.return;l=h;break}l=b.return}}function Hc(){Hf=P()+500}function Z(){return 0!==(p&6)?P():-1!==Kd?Kd:Kd=P()}function hb(a){if(0===(a.mode&1))return 1;if(0!==(p&2)&&0!==U)return U&-U;if(null!==Ik.transition)return 0===Ld&&(Ld=Dg()),Ld;a=z;if(0!==a)return a;a=window.event;a=void 0===a?16:Lg(a.type);return a}function xa(a,b,c,d){if(50<Ic)throw Ic=0,Pf=null,Error(m(185));ic(a,c,d);if(0===(p&2)||a!==O)a===O&&(0===(p&2)&&(Md|=c),4===L&&kb(a,U)),ia(a,d),1===c&&0===p&&0===(b.mode&1)&&
|
||||
(Hc(),md&&db())}function ia(a,b){var c=a.callbackNode;tj(a,b);var d=Vc(a,a===O?U:0);if(0===d)null!==c&&Ki(c),a.callbackNode=null,a.callbackPriority=0;else if(b=d&-d,a.callbackPriority!==b){null!=c&&Ki(c);if(1===b)0===a.tag?jk(Li.bind(null,a)):wh(Li.bind(null,a)),Jk(function(){0===(p&6)&&db()}),c=null;else{switch(Eg(d)){case 1:c=De;break;case 4:c=Mg;break;case 16:c=ad;break;case 536870912:c=Ng;break;default:c=ad}c=Mi(c,Ni.bind(null,a))}a.callbackPriority=b;a.callbackNode=c}}function Ni(a,b){Kd=-1;
|
||||
Ld=0;if(0!==(p&6))throw Error(m(327));var c=a.callbackNode;if(Xb()&&a.callbackNode!==c)return null;var d=Vc(a,a===O?U:0);if(0===d)return null;if(0!==(d&30)||0!==(d&a.expiredLanes)||b)b=Nd(a,d);else{b=d;var e=p;p|=2;var f=Oi();if(O!==a||U!==b)Ra=null,Hc(),wb(a,b);do try{Kk();break}catch(h){Pi(a,h)}while(1);af();Od.current=f;p=e;null!==H?b=0:(O=null,U=0,b=L)}if(0!==b){2===b&&(e=ve(a),0!==e&&(d=e,b=Qf(a,e)));if(1===b)throw c=Jc,wb(a,0),kb(a,d),ia(a,P()),c;if(6===b)kb(a,d);else{e=a.current.alternate;
|
||||
if(0===(d&30)&&!Lk(e)&&(b=Nd(a,d),2===b&&(f=ve(a),0!==f&&(d=f,b=Qf(a,f))),1===b))throw c=Jc,wb(a,0),kb(a,d),ia(a,P()),c;a.finishedWork=e;a.finishedLanes=d;switch(b){case 0:case 1:throw Error(m(345));case 2:xb(a,ja,Ra);break;case 3:kb(a,d);if((d&130023424)===d&&(b=Of+500-P(),10<b)){if(0!==Vc(a,0))break;e=a.suspendedLanes;if((e&d)!==d){Z();a.pingedLanes|=a.suspendedLanes&e;break}a.timeoutHandle=Rf(xb.bind(null,a,ja,Ra),b);break}xb(a,ja,Ra);break;case 4:kb(a,d);if((d&4194240)===d)break;b=a.eventTimes;
|
||||
for(e=-1;0<d;){var g=31-ta(d);f=1<<g;g=b[g];g>e&&(e=g);d&=~f}d=e;d=P()-d;d=(120>d?120:480>d?480:1080>d?1080:1920>d?1920:3E3>d?3E3:4320>d?4320:1960*Mk(d/1960))-d;if(10<d){a.timeoutHandle=Rf(xb.bind(null,a,ja,Ra),d);break}xb(a,ja,Ra);break;case 5:xb(a,ja,Ra);break;default:throw Error(m(329));}}}ia(a,P());return a.callbackNode===c?Ni.bind(null,a):null}function Qf(a,b){var c=Kc;a.current.memoizedState.isDehydrated&&(wb(a,b).flags|=256);a=Nd(a,b);2!==a&&(b=ja,ja=c,null!==b&&Gf(b));return a}function Gf(a){null===
|
||||
ja?ja=a:ja.push.apply(ja,a)}function Lk(a){for(var b=a;;){if(b.flags&16384){var c=b.updateQueue;if(null!==c&&(c=c.stores,null!==c))for(var d=0;d<c.length;d++){var e=c[d],f=e.getSnapshot;e=e.value;try{if(!ua(f(),e))return!1}catch(g){return!1}}}c=b.child;if(b.subtreeFlags&16384&&null!==c)c.return=b,b=c;else{if(b===a)break;for(;null===b.sibling;){if(null===b.return||b.return===a)return!0;b=b.return}b.sibling.return=b.return;b=b.sibling}}return!0}function kb(a,b){b&=~Sf;b&=~Md;a.suspendedLanes|=b;a.pingedLanes&=
|
||||
~b;for(a=a.expirationTimes;0<b;){var c=31-ta(b),d=1<<c;a[c]=-1;b&=~d}}function Li(a){if(0!==(p&6))throw Error(m(327));Xb();var b=Vc(a,0);if(0===(b&1))return ia(a,P()),null;var c=Nd(a,b);if(0!==a.tag&&2===c){var d=ve(a);0!==d&&(b=d,c=Qf(a,d))}if(1===c)throw c=Jc,wb(a,0),kb(a,b),ia(a,P()),c;if(6===c)throw Error(m(345));a.finishedWork=a.current.alternate;a.finishedLanes=b;xb(a,ja,Ra);ia(a,P());return null}function Tf(a,b){var c=p;p|=1;try{return a(b)}finally{p=c,0===p&&(Hc(),md&&db())}}function yb(a){null!==
|
||||
lb&&0===lb.tag&&0===(p&6)&&Xb();var b=p;p|=1;var c=ca.transition,d=z;try{if(ca.transition=null,z=1,a)return a()}finally{z=d,ca.transition=c,p=b,0===(p&6)&&db()}}function wb(a,b){a.finishedWork=null;a.finishedLanes=0;var c=a.timeoutHandle;-1!==c&&(a.timeoutHandle=-1,Nk(c));if(null!==H)for(c=H.return;null!==c;){var d=c;Ve(d);switch(d.tag){case 1:d=d.type.childContextTypes;null!==d&&void 0!==d&&(v(S),v(J));break;case 3:Tb();v(S);v(J);jf();break;case 5:hf(d);break;case 4:Tb();break;case 13:v(F);break;
|
||||
case 19:v(F);break;case 10:cf(d.type._context);break;case 22:case 23:ba=Ga.current,v(Ga)}c=c.return}O=a;H=a=eb(a.current,null);U=ba=b;L=0;Jc=null;Sf=Md=ra=0;ja=Kc=null;if(null!==tb){for(b=0;b<tb.length;b++)if(c=tb[b],d=c.interleaved,null!==d){c.interleaved=null;var e=d.next,f=c.pending;if(null!==f){var g=f.next;f.next=e;d.next=g}c.pending=d}tb=null}return a}function Pi(a,b){do{var c=H;try{af();yd.current=zd;if(Ad){for(var d=C.memoizedState;null!==d;){var e=d.queue;null!==e&&(e.pending=null);d=d.next}Ad=
|
||||
!1}vb=0;N=K=C=null;zc=!1;Ac=0;Uf.current=null;if(null===c||null===c.return){L=1;Jc=b;H=null;break}a:{var f=a,g=c.return,h=c,k=b;b=U;h.flags|=32768;if(null!==k&&"object"===typeof k&&"function"===typeof k.then){var n=k,l=h,p=l.tag;if(0===(l.mode&1)&&(0===p||11===p||15===p)){var r=l.alternate;r?(l.updateQueue=r.updateQueue,l.memoizedState=r.memoizedState,l.lanes=r.lanes):(l.updateQueue=null,l.memoizedState=null)}var v=ji(g);if(null!==v){v.flags&=-257;ki(v,g,h,f,b);v.mode&1&&ii(f,n,b);b=v;k=n;var x=b.updateQueue;
|
||||
if(null===x){var z=new Set;z.add(k);b.updateQueue=z}else x.add(k);break a}else{if(0===(b&1)){ii(f,n,b);Ef();break a}k=Error(m(426))}}else if(D&&h.mode&1){var y=ji(g);if(null!==y){0===(y.flags&65536)&&(y.flags|=256);ki(y,g,h,f,b);Ye(Ub(k,h));break a}}f=k=Ub(k,h);4!==L&&(L=2);null===Kc?Kc=[f]:Kc.push(f);f=g;do{switch(f.tag){case 3:f.flags|=65536;b&=-b;f.lanes|=b;var w=gi(f,k,b);Gh(f,w);break a;case 1:h=k;var A=f.type,t=f.stateNode;if(0===(f.flags&128)&&("function"===typeof A.getDerivedStateFromError||
|
||||
null!==t&&"function"===typeof t.componentDidCatch&&(null===ib||!ib.has(t)))){f.flags|=65536;b&=-b;f.lanes|=b;var B=hi(f,h,b);Gh(f,B);break a}}f=f.return}while(null!==f)}Qi(c)}catch(ma){b=ma;H===c&&null!==c&&(H=c=c.return);continue}break}while(1)}function Oi(){var a=Od.current;Od.current=zd;return null===a?zd:a}function Ef(){if(0===L||3===L||2===L)L=4;null===O||0===(ra&268435455)&&0===(Md&268435455)||kb(O,U)}function Nd(a,b){var c=p;p|=2;var d=Oi();if(O!==a||U!==b)Ra=null,wb(a,b);do try{Ok();break}catch(e){Pi(a,
|
||||
e)}while(1);af();p=c;Od.current=d;if(null!==H)throw Error(m(261));O=null;U=0;return L}function Ok(){for(;null!==H;)Ri(H)}function Kk(){for(;null!==H&&!Pk();)Ri(H)}function Ri(a){var b=Qk(a.alternate,a,ba);a.memoizedProps=a.pendingProps;null===b?Qi(a):H=b;Uf.current=null}function Qi(a){var b=a;do{var c=b.alternate;a=b.return;if(0===(b.flags&32768)){if(c=xk(c,b,ba),null!==c){H=c;return}}else{c=Bk(c,b);if(null!==c){c.flags&=32767;H=c;return}if(null!==a)a.flags|=32768,a.subtreeFlags=0,a.deletions=null;
|
||||
else{L=6;H=null;return}}b=b.sibling;if(null!==b){H=b;return}H=b=a}while(null!==b);0===L&&(L=5)}function xb(a,b,c){var d=z,e=ca.transition;try{ca.transition=null,z=1,Rk(a,b,c,d)}finally{ca.transition=e,z=d}return null}function Rk(a,b,c,d){do Xb();while(null!==lb);if(0!==(p&6))throw Error(m(327));c=a.finishedWork;var e=a.finishedLanes;if(null===c)return null;a.finishedWork=null;a.finishedLanes=0;if(c===a.current)throw Error(m(177));a.callbackNode=null;a.callbackPriority=0;var f=c.lanes|c.childLanes;
|
||||
uj(a,f);a===O&&(H=O=null,U=0);0===(c.subtreeFlags&2064)&&0===(c.flags&2064)||Pd||(Pd=!0,Mi(ad,function(){Xb();return null}));f=0!==(c.flags&15990);if(0!==(c.subtreeFlags&15990)||f){f=ca.transition;ca.transition=null;var g=z;z=1;var h=p;p|=4;Uf.current=null;Ck(a,c);Fi(c,a);Tj(Kf);Zc=!!Jf;Kf=Jf=null;a.current=c;Hk(c,a,e);Sk();p=h;z=g;ca.transition=f}else a.current=c;Pd&&(Pd=!1,lb=a,Qd=e);f=a.pendingLanes;0===f&&(ib=null);oj(c.stateNode,d);ia(a,P());if(null!==b)for(d=a.onRecoverableError,c=0;c<b.length;c++)e=
|
||||
b[c],d(e.value,{componentStack:e.stack,digest:e.digest});if(Ed)throw Ed=!1,a=xf,xf=null,a;0!==(Qd&1)&&0!==a.tag&&Xb();f=a.pendingLanes;0!==(f&1)?a===Pf?Ic++:(Ic=0,Pf=a):Ic=0;db();return null}function Xb(){if(null!==lb){var a=Eg(Qd),b=ca.transition,c=z;try{ca.transition=null;z=16>a?16:a;if(null===lb)var d=!1;else{a=lb;lb=null;Qd=0;if(0!==(p&6))throw Error(m(331));var e=p;p|=4;for(l=a.current;null!==l;){var f=l,g=f.child;if(0!==(l.flags&16)){var h=f.deletions;if(null!==h){for(var k=0;k<h.length;k++){var n=
|
||||
h[k];for(l=n;null!==l;){var q=l;switch(q.tag){case 0:case 11:case 15:Gc(8,q,f)}var u=q.child;if(null!==u)u.return=q,l=u;else for(;null!==l;){q=l;var r=q.sibling,v=q.return;Ai(q);if(q===n){l=null;break}if(null!==r){r.return=v;l=r;break}l=v}}}var x=f.alternate;if(null!==x){var y=x.child;if(null!==y){x.child=null;do{var C=y.sibling;y.sibling=null;y=C}while(null!==y)}}l=f}}if(0!==(f.subtreeFlags&2064)&&null!==g)g.return=f,l=g;else b:for(;null!==l;){f=l;if(0!==(f.flags&2048))switch(f.tag){case 0:case 11:case 15:Gc(9,
|
||||
f,f.return)}var w=f.sibling;if(null!==w){w.return=f.return;l=w;break b}l=f.return}}var A=a.current;for(l=A;null!==l;){g=l;var t=g.child;if(0!==(g.subtreeFlags&2064)&&null!==t)t.return=g,l=t;else b:for(g=A;null!==l;){h=l;if(0!==(h.flags&2048))try{switch(h.tag){case 0:case 11:case 15:Id(9,h)}}catch(ma){G(h,h.return,ma)}if(h===g){l=null;break b}var B=h.sibling;if(null!==B){B.return=h.return;l=B;break b}l=h.return}}p=e;db();if(Ca&&"function"===typeof Ca.onPostCommitFiberRoot)try{Ca.onPostCommitFiberRoot(Uc,
|
||||
a)}catch(ma){}d=!0}return d}finally{z=c,ca.transition=b}}return!1}function Si(a,b,c){b=Ub(c,b);b=gi(a,b,1);a=fb(a,b,1);b=Z();null!==a&&(ic(a,1,b),ia(a,b))}function G(a,b,c){if(3===a.tag)Si(a,a,c);else for(;null!==b;){if(3===b.tag){Si(b,a,c);break}else if(1===b.tag){var d=b.stateNode;if("function"===typeof b.type.getDerivedStateFromError||"function"===typeof d.componentDidCatch&&(null===ib||!ib.has(d))){a=Ub(c,a);a=hi(b,a,1);b=fb(b,a,1);a=Z();null!==b&&(ic(b,1,a),ia(b,a));break}}b=b.return}}function sk(a,
|
||||
b,c){var d=a.pingCache;null!==d&&d.delete(b);b=Z();a.pingedLanes|=a.suspendedLanes&c;O===a&&(U&c)===c&&(4===L||3===L&&(U&130023424)===U&&500>P()-Of?wb(a,0):Sf|=c);ia(a,b)}function Ti(a,b){0===b&&(0===(a.mode&1)?b=1:(b=Rd,Rd<<=1,0===(Rd&130023424)&&(Rd=4194304)));var c=Z();a=Oa(a,b);null!==a&&(ic(a,b,c),ia(a,c))}function vk(a){var b=a.memoizedState,c=0;null!==b&&(c=b.retryLane);Ti(a,c)}function Gk(a,b){var c=0;switch(a.tag){case 13:var d=a.stateNode;var e=a.memoizedState;null!==e&&(c=e.retryLane);
|
||||
break;case 19:d=a.stateNode;break;default:throw Error(m(314));}null!==d&&d.delete(b);Ti(a,c)}function Mi(a,b){return xh(a,b)}function Tk(a,b,c,d){this.tag=a;this.key=c;this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null;this.index=0;this.ref=null;this.pendingProps=b;this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null;this.mode=d;this.subtreeFlags=this.flags=0;this.deletions=null;this.childLanes=this.lanes=0;this.alternate=null}function yf(a){a=
|
||||
a.prototype;return!(!a||!a.isReactComponent)}function Uk(a){if("function"===typeof a)return yf(a)?1:0;if(void 0!==a&&null!==a){a=a.$$typeof;if(a===ie)return 11;if(a===je)return 14}return 2}function eb(a,b){var c=a.alternate;null===c?(c=pa(a.tag,b,a.key,a.mode),c.elementType=a.elementType,c.type=a.type,c.stateNode=a.stateNode,c.alternate=a,a.alternate=c):(c.pendingProps=b,c.type=a.type,c.flags=0,c.subtreeFlags=0,c.deletions=null);c.flags=a.flags&14680064;c.childLanes=a.childLanes;c.lanes=a.lanes;c.child=
|
||||
a.child;c.memoizedProps=a.memoizedProps;c.memoizedState=a.memoizedState;c.updateQueue=a.updateQueue;b=a.dependencies;c.dependencies=null===b?null:{lanes:b.lanes,firstContext:b.firstContext};c.sibling=a.sibling;c.index=a.index;c.ref=a.ref;return c}function rd(a,b,c,d,e,f){var g=2;d=a;if("function"===typeof a)yf(a)&&(g=1);else if("string"===typeof a)g=5;else a:switch(a){case Bb:return sb(c.children,e,f,b);case fe:g=8;e|=8;break;case ee:return a=pa(12,c,b,e|2),a.elementType=ee,a.lanes=f,a;case ge:return a=
|
||||
pa(13,c,b,e),a.elementType=ge,a.lanes=f,a;case he:return a=pa(19,c,b,e),a.elementType=he,a.lanes=f,a;case Ui:return Gd(c,e,f,b);default:if("object"===typeof a&&null!==a)switch(a.$$typeof){case hg:g=10;break a;case gg:g=9;break a;case ie:g=11;break a;case je:g=14;break a;case Ta:g=16;d=null;break a}throw Error(m(130,null==a?a:typeof a,""));}b=pa(g,c,b,e);b.elementType=a;b.type=d;b.lanes=f;return b}function sb(a,b,c,d){a=pa(7,a,d,b);a.lanes=c;return a}function Gd(a,b,c,d){a=pa(22,a,d,b);a.elementType=
|
||||
Ui;a.lanes=c;a.stateNode={isHidden:!1};return a}function Ze(a,b,c){a=pa(6,a,null,b);a.lanes=c;return a}function $e(a,b,c){b=pa(4,null!==a.children?a.children:[],a.key,b);b.lanes=c;b.stateNode={containerInfo:a.containerInfo,pendingChildren:null,implementation:a.implementation};return b}function Vk(a,b,c,d,e){this.tag=b;this.containerInfo=a;this.finishedWork=this.pingCache=this.current=this.pendingChildren=null;this.timeoutHandle=-1;this.callbackNode=this.pendingContext=this.context=null;this.callbackPriority=
|
||||
0;this.eventTimes=we(0);this.expirationTimes=we(-1);this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0;this.entanglements=we(0);this.identifierPrefix=d;this.onRecoverableError=e;this.mutableSourceEagerHydrationData=null}function Vf(a,b,c,d,e,f,g,h,k,l){a=new Vk(a,b,c,h,k);1===b?(b=1,!0===f&&(b|=8)):b=0;f=pa(3,null,null,b);a.current=f;f.stateNode=a;f.memoizedState={element:d,isDehydrated:c,cache:null,transitions:null,
|
||||
pendingSuspenseBoundaries:null};ff(f);return a}function Wk(a,b,c){var d=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:Cb,key:null==d?null:""+d,children:a,containerInfo:b,implementation:c}}function Vi(a){if(!a)return cb;a=a._reactInternals;a:{if(nb(a)!==a||1!==a.tag)throw Error(m(170));var b=a;do{switch(b.tag){case 3:b=b.stateNode.context;break a;case 1:if(ea(b.type)){b=b.stateNode.__reactInternalMemoizedMergedChildContext;break a}}b=b.return}while(null!==b);throw Error(m(171));
|
||||
}if(1===a.tag){var c=a.type;if(ea(c))return uh(a,c,b)}return b}function Wi(a,b,c,d,e,f,g,h,k,l){a=Vf(c,d,!0,a,e,f,g,h,k);a.context=Vi(null);c=a.current;d=Z();e=hb(c);f=Pa(d,e);f.callback=void 0!==b&&null!==b?b:null;fb(c,f,e);a.current.lanes=e;ic(a,e,d);ia(a,d);return a}function Sd(a,b,c,d){var e=b.current,f=Z(),g=hb(e);c=Vi(c);null===b.context?b.context=c:b.pendingContext=c;b=Pa(f,g);b.payload={element:a};d=void 0===d?null:d;null!==d&&(b.callback=d);a=fb(e,b,g);null!==a&&(xa(a,e,g,f),vd(a,e,g));return g}
|
||||
function Td(a){a=a.current;if(!a.child)return null;switch(a.child.tag){case 5:return a.child.stateNode;default:return a.child.stateNode}}function Xi(a,b){a=a.memoizedState;if(null!==a&&null!==a.dehydrated){var c=a.retryLane;a.retryLane=0!==c&&c<b?c:b}}function Wf(a,b){Xi(a,b);(a=a.alternate)&&Xi(a,b)}function Xk(a){a=Bg(a);return null===a?null:a.stateNode}function Yk(a){return null}function Xf(a){this._internalRoot=a}function Ud(a){this._internalRoot=a}function Yf(a){return!(!a||1!==a.nodeType&&9!==
|
||||
a.nodeType&&11!==a.nodeType)}function Vd(a){return!(!a||1!==a.nodeType&&9!==a.nodeType&&11!==a.nodeType&&(8!==a.nodeType||" react-mount-point-unstable "!==a.nodeValue))}function Yi(){}function Zk(a,b,c,d,e){if(e){if("function"===typeof d){var f=d;d=function(){var a=Td(g);f.call(a)}}var g=Wi(b,d,a,0,null,!1,!1,"",Yi);a._reactRootContainer=g;a[Ja]=g.current;sc(8===a.nodeType?a.parentNode:a);yb();return g}for(;e=a.lastChild;)a.removeChild(e);if("function"===typeof d){var h=d;d=function(){var a=Td(k);
|
||||
h.call(a)}}var k=Vf(a,0,!1,null,null,!1,!1,"",Yi);a._reactRootContainer=k;a[Ja]=k.current;sc(8===a.nodeType?a.parentNode:a);yb(function(){Sd(b,k,c,d)});return k}function Wd(a,b,c,d,e){var f=c._reactRootContainer;if(f){var g=f;if("function"===typeof e){var h=e;e=function(){var a=Td(g);h.call(a)}}Sd(b,g,a,e)}else g=Zk(c,b,a,e,d);return Td(g)}var cg=new Set,$b={},Ia=!("undefined"===typeof window||"undefined"===typeof window.document||"undefined"===typeof window.document.createElement),Zd=Object.prototype.hasOwnProperty,
|
||||
cj=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,eg={},dg={},R={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(a){R[a]=
|
||||
new Y(a,0,!1,a,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(a){var b=a[0];R[b]=new Y(b,1,!1,a[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(a){R[a]=new Y(a,2,!1,a.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(a){R[a]=new Y(a,2,!1,a,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(a){R[a]=
|
||||
new Y(a,3,!1,a.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(a){R[a]=new Y(a,3,!0,a,null,!1,!1)});["capture","download"].forEach(function(a){R[a]=new Y(a,4,!1,a,null,!1,!1)});["cols","rows","size","span"].forEach(function(a){R[a]=new Y(a,6,!1,a,null,!1,!1)});["rowSpan","start"].forEach(function(a){R[a]=new Y(a,5,!1,a.toLowerCase(),null,!1,!1)});var Zf=/[\-:]([a-z])/g,$f=function(a){return a[1].toUpperCase()};"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(a){var b=
|
||||
a.replace(Zf,$f);R[b]=new Y(b,1,!1,a,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(a){var b=a.replace(Zf,$f);R[b]=new Y(b,1,!1,a,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(a){var b=a.replace(Zf,$f);R[b]=new Y(b,1,!1,a,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(a){R[a]=new Y(a,1,!1,a.toLowerCase(),null,!1,!1)});R.xlinkHref=new Y("xlinkHref",
|
||||
1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(a){R[a]=new Y(a,1,!1,a.toLowerCase(),null,!0,!0)});var Sa=zb.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,sd=Symbol.for("react.element"),Cb=Symbol.for("react.portal"),Bb=Symbol.for("react.fragment"),fe=Symbol.for("react.strict_mode"),ee=Symbol.for("react.profiler"),hg=Symbol.for("react.provider"),gg=Symbol.for("react.context"),ie=Symbol.for("react.forward_ref"),ge=Symbol.for("react.suspense"),
|
||||
he=Symbol.for("react.suspense_list"),je=Symbol.for("react.memo"),Ta=Symbol.for("react.lazy");Symbol.for("react.scope");Symbol.for("react.debug_trace_mode");var Ui=Symbol.for("react.offscreen");Symbol.for("react.legacy_hidden");Symbol.for("react.cache");Symbol.for("react.tracing_marker");var fg=Symbol.iterator,E=Object.assign,ae,ce=!1,cc=Array.isArray,Xd,yi=function(a){return"undefined"!==typeof MSApp&&MSApp.execUnsafeLocalFunction?function(b,c,d,e){MSApp.execUnsafeLocalFunction(function(){return a(b,
|
||||
c,d,e)})}:a}(function(a,b){if("http://www.w3.org/2000/svg"!==a.namespaceURI||"innerHTML"in a)a.innerHTML=b;else{Xd=Xd||document.createElement("div");Xd.innerHTML="<svg>"+b.valueOf().toString()+"</svg>";for(b=Xd.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;b.firstChild;)a.appendChild(b.firstChild)}}),Fc=function(a,b){if(b){var c=a.firstChild;if(c&&c===a.lastChild&&3===c.nodeType){c.nodeValue=b;return}}a.textContent=b},dc={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,
|
||||
borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,
|
||||
strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},$k=["Webkit","ms","Moz","O"];Object.keys(dc).forEach(function(a){$k.forEach(function(b){b=b+a.charAt(0).toUpperCase()+a.substring(1);dc[b]=dc[a]})});var ij=E({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),ze=null,se=null,Eb=null,Fb=null,xg=function(a,b){return a(b)},yg=function(){},te=!1,Oe=!1;if(Ia)try{var Lc={};Object.defineProperty(Lc,
|
||||
"passive",{get:function(){Oe=!0}});window.addEventListener("test",Lc,Lc);window.removeEventListener("test",Lc,Lc)}catch(a){Oe=!1}var kj=function(a,b,c,d,e,f,g,h,k){var l=Array.prototype.slice.call(arguments,3);try{b.apply(c,l)}catch(q){this.onError(q)}},gc=!1,Sc=null,Tc=!1,ue=null,lj={onError:function(a){gc=!0;Sc=a}},Ba=zb.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler,Jg=Ba.unstable_scheduleCallback,Kg=Ba.unstable_NormalPriority,xh=Jg,Ki=Ba.unstable_cancelCallback,Pk=Ba.unstable_shouldYield,
|
||||
Sk=Ba.unstable_requestPaint,P=Ba.unstable_now,Dj=Ba.unstable_getCurrentPriorityLevel,De=Ba.unstable_ImmediatePriority,Mg=Ba.unstable_UserBlockingPriority,ad=Kg,Ej=Ba.unstable_LowPriority,Ng=Ba.unstable_IdlePriority,Uc=null,Ca=null,ta=Math.clz32?Math.clz32:pj,qj=Math.log,rj=Math.LN2,Wc=64,Rd=4194304,z=0,Ae=!1,Yc=[],Va=null,Wa=null,Xa=null,jc=new Map,kc=new Map,Ya=[],Bj="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset submit".split(" "),
|
||||
Gb=Sa.ReactCurrentBatchConfig,Zc=!0,$c=null,Za=null,Ee=null,bd=null,Yb={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(a){return a.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},He=ka(Yb),Mc=E({},Yb,{view:0,detail:0}),ak=ka(Mc),ag,bg,Nc,Yd=E({},Mc,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:Fe,button:0,buttons:0,relatedTarget:function(a){return void 0===a.relatedTarget?a.fromElement===a.srcElement?a.toElement:a.fromElement:
|
||||
a.relatedTarget},movementX:function(a){if("movementX"in a)return a.movementX;a!==Nc&&(Nc&&"mousemove"===a.type?(ag=a.screenX-Nc.screenX,bg=a.screenY-Nc.screenY):bg=ag=0,Nc=a);return ag},movementY:function(a){return"movementY"in a?a.movementY:bg}}),ih=ka(Yd),al=E({},Yd,{dataTransfer:0}),Wj=ka(al),bl=E({},Mc,{relatedTarget:0}),Pe=ka(bl),cl=E({},Yb,{animationName:0,elapsedTime:0,pseudoElement:0}),Yj=ka(cl),dl=E({},Yb,{clipboardData:function(a){return"clipboardData"in a?a.clipboardData:window.clipboardData}}),
|
||||
ck=ka(dl),el=E({},Yb,{data:0}),qh=ka(el),fk=qh,fl={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},gl={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",
|
||||
112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},Gj={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"},hl=E({},Mc,{key:function(a){if(a.key){var b=fl[a.key]||a.key;if("Unidentified"!==b)return b}return"keypress"===a.type?(a=cd(a),13===a?"Enter":String.fromCharCode(a)):"keydown"===a.type||"keyup"===a.type?gl[a.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,
|
||||
metaKey:0,repeat:0,locale:0,getModifierState:Fe,charCode:function(a){return"keypress"===a.type?cd(a):0},keyCode:function(a){return"keydown"===a.type||"keyup"===a.type?a.keyCode:0},which:function(a){return"keypress"===a.type?cd(a):"keydown"===a.type||"keyup"===a.type?a.keyCode:0}}),Vj=ka(hl),il=E({},Yd,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0}),nh=ka(il),jl=E({},Mc,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,
|
||||
ctrlKey:0,shiftKey:0,getModifierState:Fe}),Xj=ka(jl),kl=E({},Yb,{propertyName:0,elapsedTime:0,pseudoElement:0}),Zj=ka(kl),ll=E({},Yd,{deltaX:function(a){return"deltaX"in a?a.deltaX:"wheelDeltaX"in a?-a.wheelDeltaX:0},deltaY:function(a){return"deltaY"in a?a.deltaY:"wheelDeltaY"in a?-a.wheelDeltaY:"wheelDelta"in a?-a.wheelDelta:0},deltaZ:0,deltaMode:0}),bk=ka(ll),Hj=[9,13,27,32],Ge=Ia&&"CompositionEvent"in window,Oc=null;Ia&&"documentMode"in document&&(Oc=document.documentMode);var ek=Ia&&"TextEvent"in
|
||||
window&&!Oc,Ug=Ia&&(!Ge||Oc&&8<Oc&&11>=Oc),Tg=String.fromCharCode(32),Sg=!1,Hb=!1,Kj={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0},oc=null,pc=null,ph=!1;Ia&&(ph=Lj("input")&&(!document.documentMode||9<document.documentMode));var ua="function"===typeof Object.is?Object.is:Sj,dk=Ia&&"documentMode"in document&&11>=document.documentMode,Jb=null,Ke=null,rc=null,Je=!1,Kb={animationend:gd("Animation","AnimationEnd"),
|
||||
animationiteration:gd("Animation","AnimationIteration"),animationstart:gd("Animation","AnimationStart"),transitionend:gd("Transition","TransitionEnd")},Le={},eh={};Ia&&(eh=document.createElement("div").style,"AnimationEvent"in window||(delete Kb.animationend.animation,delete Kb.animationiteration.animation,delete Kb.animationstart.animation),"TransitionEvent"in window||delete Kb.transitionend.transition);var jh=hd("animationend"),kh=hd("animationiteration"),lh=hd("animationstart"),mh=hd("transitionend"),
|
||||
fh=new Map,Zi="abort auxClick cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");
|
||||
(function(){for(var a=0;a<Zi.length;a++){var b=Zi[a],c=b.toLowerCase();b=b[0].toUpperCase()+b.slice(1);$a(c,"on"+b)}$a(jh,"onAnimationEnd");$a(kh,"onAnimationIteration");$a(lh,"onAnimationStart");$a("dblclick","onDoubleClick");$a("focusin","onFocus");$a("focusout","onBlur");$a(mh,"onTransitionEnd")})();Ab("onMouseEnter",["mouseout","mouseover"]);Ab("onMouseLeave",["mouseout","mouseover"]);Ab("onPointerEnter",["pointerout","pointerover"]);Ab("onPointerLeave",["pointerout","pointerover"]);mb("onChange",
|
||||
"change click focusin focusout input keydown keyup selectionchange".split(" "));mb("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" "));mb("onBeforeInput",["compositionend","keypress","textInput","paste"]);mb("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" "));mb("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" "));mb("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));
|
||||
var Ec="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Uj=new Set("cancel close invalid load scroll toggle".split(" ").concat(Ec)),id="_reactListening"+Math.random().toString(36).slice(2),gk=/\r\n?/g,hk=/\u0000|\uFFFD/g,Jf=null,Kf=null,Rf="function"===typeof setTimeout?setTimeout:void 0,Nk="function"===typeof clearTimeout?
|
||||
clearTimeout:void 0,$i="function"===typeof Promise?Promise:void 0,Jk="function"===typeof queueMicrotask?queueMicrotask:"undefined"!==typeof $i?function(a){return $i.resolve(null).then(a).catch(ik)}:Rf,Zb=Math.random().toString(36).slice(2),Da="__reactFiber$"+Zb,uc="__reactProps$"+Zb,Ja="__reactContainer$"+Zb,Me="__reactEvents$"+Zb,Dk="__reactListeners$"+Zb,Ek="__reactHandles$"+Zb,Se=[],Mb=-1,cb={},J=bb(cb),S=bb(!1),pb=cb,La=null,md=!1,Te=!1,Ob=[],Pb=0,od=null,nd=0,na=[],oa=0,rb=null,Ma=1,Na="",la=
|
||||
null,fa=null,D=!1,wa=null,Ik=Sa.ReactCurrentBatchConfig,Vb=Dh(!0),li=Dh(!1),ud=bb(null),td=null,Rb=null,bf=null,tb=null,kk=Oa,gb=!1,wc={},Ea=bb(wc),yc=bb(wc),xc=bb(wc),F=bb(0),kf=[],yd=Sa.ReactCurrentDispatcher,sf=Sa.ReactCurrentBatchConfig,vb=0,C=null,K=null,N=null,Ad=!1,zc=!1,Ac=0,ml=0,zd={readContext:qa,useCallback:V,useContext:V,useEffect:V,useImperativeHandle:V,useInsertionEffect:V,useLayoutEffect:V,useMemo:V,useReducer:V,useRef:V,useState:V,useDebugValue:V,useDeferredValue:V,useTransition:V,
|
||||
useMutableSource:V,useSyncExternalStore:V,useId:V,unstable_isNewReconciler:!1},lk={readContext:qa,useCallback:function(a,b){Fa().memoizedState=[a,void 0===b?null:b];return a},useContext:qa,useEffect:Sh,useImperativeHandle:function(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;return Bd(4194308,4,Vh.bind(null,b,a),c)},useLayoutEffect:function(a,b){return Bd(4194308,4,a,b)},useInsertionEffect:function(a,b){return Bd(4,2,a,b)},useMemo:function(a,b){var c=Fa();b=void 0===b?null:b;a=a();c.memoizedState=
|
||||
[a,b];return a},useReducer:function(a,b,c){var d=Fa();b=void 0!==c?c(b):b;d.memoizedState=d.baseState=b;a={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:a,lastRenderedState:b};d.queue=a;a=a.dispatch=qk.bind(null,C,a);return[d.memoizedState,a]},useRef:function(a){var b=Fa();a={current:a};return b.memoizedState=a},useState:Qh,useDebugValue:rf,useDeferredValue:function(a){return Fa().memoizedState=a},useTransition:function(){var a=Qh(!1),b=a[0];a=pk.bind(null,a[1]);Fa().memoizedState=
|
||||
a;return[b,a]},useMutableSource:function(a,b,c){},useSyncExternalStore:function(a,b,c){var d=C,e=Fa();if(D){if(void 0===c)throw Error(m(407));c=c()}else{c=b();if(null===O)throw Error(m(349));0!==(vb&30)||Nh(d,b,c)}e.memoizedState=c;var f={value:c,getSnapshot:b};e.queue=f;Sh(Lh.bind(null,d,f,a),[a]);d.flags|=2048;Cc(9,Mh.bind(null,d,f,c,b),void 0,null);return c},useId:function(){var a=Fa(),b=O.identifierPrefix;if(D){var c=Na;var d=Ma;c=(d&~(1<<32-ta(d)-1)).toString(32)+c;b=":"+b+"R"+c;c=Ac++;0<c&&
|
||||
(b+="H"+c.toString(32));b+=":"}else c=ml++,b=":"+b+"r"+c.toString(32)+":";return a.memoizedState=b},unstable_isNewReconciler:!1},mk={readContext:qa,useCallback:Xh,useContext:qa,useEffect:qf,useImperativeHandle:Wh,useInsertionEffect:Th,useLayoutEffect:Uh,useMemo:Yh,useReducer:of,useRef:Rh,useState:function(a){return of(Bc)},useDebugValue:rf,useDeferredValue:function(a){var b=sa();return Zh(b,K.memoizedState,a)},useTransition:function(){var a=of(Bc)[0],b=sa().memoizedState;return[a,b]},useMutableSource:Jh,
|
||||
useSyncExternalStore:Kh,useId:$h,unstable_isNewReconciler:!1},nk={readContext:qa,useCallback:Xh,useContext:qa,useEffect:qf,useImperativeHandle:Wh,useInsertionEffect:Th,useLayoutEffect:Uh,useMemo:Yh,useReducer:pf,useRef:Rh,useState:function(a){return pf(Bc)},useDebugValue:rf,useDeferredValue:function(a){var b=sa();return null===K?b.memoizedState=a:Zh(b,K.memoizedState,a)},useTransition:function(){var a=pf(Bc)[0],b=sa().memoizedState;return[a,b]},useMutableSource:Jh,useSyncExternalStore:Kh,useId:$h,
|
||||
unstable_isNewReconciler:!1},Dd={isMounted:function(a){return(a=a._reactInternals)?nb(a)===a:!1},enqueueSetState:function(a,b,c){a=a._reactInternals;var d=Z(),e=hb(a),f=Pa(d,e);f.payload=b;void 0!==c&&null!==c&&(f.callback=c);b=fb(a,f,e);null!==b&&(xa(b,a,e,d),vd(b,a,e))},enqueueReplaceState:function(a,b,c){a=a._reactInternals;var d=Z(),e=hb(a),f=Pa(d,e);f.tag=1;f.payload=b;void 0!==c&&null!==c&&(f.callback=c);b=fb(a,f,e);null!==b&&(xa(b,a,e,d),vd(b,a,e))},enqueueForceUpdate:function(a,b){a=a._reactInternals;
|
||||
var c=Z(),d=hb(a),e=Pa(c,d);e.tag=2;void 0!==b&&null!==b&&(e.callback=b);b=fb(a,e,d);null!==b&&(xa(b,a,d,c),vd(b,a,d))}},rk="function"===typeof WeakMap?WeakMap:Map,tk=Sa.ReactCurrentOwner,ha=!1,Cf={dehydrated:null,treeContext:null,retryLane:0};var zk=function(a,b,c,d){for(c=b.child;null!==c;){if(5===c.tag||6===c.tag)a.appendChild(c.stateNode);else if(4!==c.tag&&null!==c.child){c.child.return=c;c=c.child;continue}if(c===b)break;for(;null===c.sibling;){if(null===c.return||c.return===b)return;c=c.return}c.sibling.return=
|
||||
c.return;c=c.sibling}};var xi=function(a,b){};var yk=function(a,b,c,d,e){var f=a.memoizedProps;if(f!==d){a=b.stateNode;ub(Ea.current);e=null;switch(c){case "input":f=ke(a,f);d=ke(a,d);e=[];break;case "select":f=E({},f,{value:void 0});d=E({},d,{value:void 0});e=[];break;case "textarea":f=ne(a,f);d=ne(a,d);e=[];break;default:"function"!==typeof f.onClick&&"function"===typeof d.onClick&&(a.onclick=kd)}pe(c,d);var g;c=null;for(l in f)if(!d.hasOwnProperty(l)&&f.hasOwnProperty(l)&&null!=f[l])if("style"===
|
||||
l){var h=f[l];for(g in h)h.hasOwnProperty(g)&&(c||(c={}),c[g]="")}else"dangerouslySetInnerHTML"!==l&&"children"!==l&&"suppressContentEditableWarning"!==l&&"suppressHydrationWarning"!==l&&"autoFocus"!==l&&($b.hasOwnProperty(l)?e||(e=[]):(e=e||[]).push(l,null));for(l in d){var k=d[l];h=null!=f?f[l]:void 0;if(d.hasOwnProperty(l)&&k!==h&&(null!=k||null!=h))if("style"===l)if(h){for(g in h)!h.hasOwnProperty(g)||k&&k.hasOwnProperty(g)||(c||(c={}),c[g]="");for(g in k)k.hasOwnProperty(g)&&h[g]!==k[g]&&(c||
|
||||
(c={}),c[g]=k[g])}else c||(e||(e=[]),e.push(l,c)),c=k;else"dangerouslySetInnerHTML"===l?(k=k?k.__html:void 0,h=h?h.__html:void 0,null!=k&&h!==k&&(e=e||[]).push(l,k)):"children"===l?"string"!==typeof k&&"number"!==typeof k||(e=e||[]).push(l,""+k):"suppressContentEditableWarning"!==l&&"suppressHydrationWarning"!==l&&($b.hasOwnProperty(l)?(null!=k&&"onScroll"===l&&B("scroll",a),e||h===k||(e=[])):(e=e||[]).push(l,k))}c&&(e=e||[]).push("style",c);var l=e;if(b.updateQueue=l)b.flags|=4}};var Ak=function(a,
|
||||
b,c,d){c!==d&&(b.flags|=4)};var Jd=!1,X=!1,Fk="function"===typeof WeakSet?WeakSet:Set,l=null,zi=!1,T=null,za=!1,Mk=Math.ceil,Od=Sa.ReactCurrentDispatcher,Uf=Sa.ReactCurrentOwner,ca=Sa.ReactCurrentBatchConfig,p=0,O=null,H=null,U=0,ba=0,Ga=bb(0),L=0,Jc=null,ra=0,Md=0,Sf=0,Kc=null,ja=null,Of=0,Hf=Infinity,Ra=null,Ed=!1,xf=null,ib=null,Pd=!1,lb=null,Qd=0,Ic=0,Pf=null,Kd=-1,Ld=0;var Qk=function(a,b,c){if(null!==a)if(a.memoizedProps!==b.pendingProps||S.current)ha=!0;else{if(0===(a.lanes&c)&&0===(b.flags&
|
||||
128))return ha=!1,wk(a,b,c);ha=0!==(a.flags&131072)?!0:!1}else ha=!1,D&&0!==(b.flags&1048576)&&yh(b,nd,b.index);b.lanes=0;switch(b.tag){case 2:var d=b.type;Fd(a,b);a=b.pendingProps;var e=Nb(b,J.current);Sb(b,c);e=mf(null,b,d,a,e,c);var f=nf();b.flags|=1;"object"===typeof e&&null!==e&&"function"===typeof e.render&&void 0===e.$$typeof?(b.tag=1,b.memoizedState=null,b.updateQueue=null,ea(d)?(f=!0,ld(b)):f=!1,b.memoizedState=null!==e.state&&void 0!==e.state?e.state:null,ff(b),e.updater=Dd,b.stateNode=
|
||||
e,e._reactInternals=b,uf(b,d,a,c),b=Af(null,b,d,!0,f,c)):(b.tag=0,D&&f&&Ue(b),aa(null,b,e,c),b=b.child);return b;case 16:d=b.elementType;a:{Fd(a,b);a=b.pendingProps;e=d._init;d=e(d._payload);b.type=d;e=b.tag=Uk(d);a=ya(d,a);switch(e){case 0:b=zf(null,b,d,a,c);break a;case 1:b=ri(null,b,d,a,c);break a;case 11:b=mi(null,b,d,a,c);break a;case 14:b=ni(null,b,d,ya(d.type,a),c);break a}throw Error(m(306,d,""));}return b;case 0:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:ya(d,e),zf(a,b,d,e,c);
|
||||
case 1:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:ya(d,e),ri(a,b,d,e,c);case 3:a:{si(b);if(null===a)throw Error(m(387));d=b.pendingProps;f=b.memoizedState;e=f.element;Fh(a,b);wd(b,d,null,c);var g=b.memoizedState;d=g.element;if(f.isDehydrated)if(f={element:d,isDehydrated:!1,cache:g.cache,pendingSuspenseBoundaries:g.pendingSuspenseBoundaries,transitions:g.transitions},b.updateQueue.baseState=f,b.memoizedState=f,b.flags&256){e=Ub(Error(m(423)),b);b=ti(a,b,d,c,e);break a}else if(d!==e){e=
|
||||
Ub(Error(m(424)),b);b=ti(a,b,d,c,e);break a}else for(fa=Ka(b.stateNode.containerInfo.firstChild),la=b,D=!0,wa=null,c=li(b,null,d,c),b.child=c;c;)c.flags=c.flags&-3|4096,c=c.sibling;else{Qb();if(d===e){b=Qa(a,b,c);break a}aa(a,b,d,c)}b=b.child}return b;case 5:return Ih(b),null===a&&Xe(b),d=b.type,e=b.pendingProps,f=null!==a?a.memoizedProps:null,g=e.children,Qe(d,e)?g=null:null!==f&&Qe(d,f)&&(b.flags|=32),qi(a,b),aa(a,b,g,c),b.child;case 6:return null===a&&Xe(b),null;case 13:return ui(a,b,c);case 4:return gf(b,
|
||||
b.stateNode.containerInfo),d=b.pendingProps,null===a?b.child=Vb(b,null,d,c):aa(a,b,d,c),b.child;case 11:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:ya(d,e),mi(a,b,d,e,c);case 7:return aa(a,b,b.pendingProps,c),b.child;case 8:return aa(a,b,b.pendingProps.children,c),b.child;case 12:return aa(a,b,b.pendingProps.children,c),b.child;case 10:a:{d=b.type._context;e=b.pendingProps;f=b.memoizedProps;g=e.value;y(ud,d._currentValue);d._currentValue=g;if(null!==f)if(ua(f.value,g)){if(f.children===
|
||||
e.children&&!S.current){b=Qa(a,b,c);break a}}else for(f=b.child,null!==f&&(f.return=b);null!==f;){var h=f.dependencies;if(null!==h){g=f.child;for(var k=h.firstContext;null!==k;){if(k.context===d){if(1===f.tag){k=Pa(-1,c&-c);k.tag=2;var l=f.updateQueue;if(null!==l){l=l.shared;var p=l.pending;null===p?k.next=k:(k.next=p.next,p.next=k);l.pending=k}}f.lanes|=c;k=f.alternate;null!==k&&(k.lanes|=c);df(f.return,c,b);h.lanes|=c;break}k=k.next}}else if(10===f.tag)g=f.type===b.type?null:f.child;else if(18===
|
||||
f.tag){g=f.return;if(null===g)throw Error(m(341));g.lanes|=c;h=g.alternate;null!==h&&(h.lanes|=c);df(g,c,b);g=f.sibling}else g=f.child;if(null!==g)g.return=f;else for(g=f;null!==g;){if(g===b){g=null;break}f=g.sibling;if(null!==f){f.return=g.return;g=f;break}g=g.return}f=g}aa(a,b,e.children,c);b=b.child}return b;case 9:return e=b.type,d=b.pendingProps.children,Sb(b,c),e=qa(e),d=d(e),b.flags|=1,aa(a,b,d,c),b.child;case 14:return d=b.type,e=ya(d,b.pendingProps),e=ya(d.type,e),ni(a,b,d,e,c);case 15:return oi(a,
|
||||
b,b.type,b.pendingProps,c);case 17:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:ya(d,e),Fd(a,b),b.tag=1,ea(d)?(a=!0,ld(b)):a=!1,Sb(b,c),ei(b,d,e),uf(b,d,e,c),Af(null,b,d,!0,a,c);case 19:return wi(a,b,c);case 22:return pi(a,b,c)}throw Error(m(156,b.tag));};var pa=function(a,b,c,d){return new Tk(a,b,c,d)},aj="function"===typeof reportError?reportError:function(a){console.error(a)};Ud.prototype.render=Xf.prototype.render=function(a){var b=this._internalRoot;if(null===b)throw Error(m(409));
|
||||
Sd(a,b,null,null)};Ud.prototype.unmount=Xf.prototype.unmount=function(){var a=this._internalRoot;if(null!==a){this._internalRoot=null;var b=a.containerInfo;yb(function(){Sd(null,a,null,null)});b[Ja]=null}};Ud.prototype.unstable_scheduleHydration=function(a){if(a){var b=nl();a={blockedOn:null,target:a,priority:b};for(var c=0;c<Ya.length&&0!==b&&b<Ya[c].priority;c++);Ya.splice(c,0,a);0===c&&Hg(a)}};var Cj=function(a){switch(a.tag){case 3:var b=a.stateNode;if(b.current.memoizedState.isDehydrated){var c=
|
||||
hc(b.pendingLanes);0!==c&&(xe(b,c|1),ia(b,P()),0===(p&6)&&(Hc(),db()))}break;case 13:yb(function(){var b=Oa(a,1);if(null!==b){var c=Z();xa(b,a,1,c)}}),Wf(a,1)}};var Gg=function(a){if(13===a.tag){var b=Oa(a,134217728);if(null!==b){var c=Z();xa(b,a,134217728,c)}Wf(a,134217728)}};var xj=function(a){if(13===a.tag){var b=hb(a),c=Oa(a,b);if(null!==c){var d=Z();xa(c,a,b,d)}Wf(a,b)}};var nl=function(){return z};var wj=function(a,b){var c=z;try{return z=a,b()}finally{z=c}};se=function(a,b,c){switch(b){case "input":le(a,
|
||||
c);b=c.name;if("radio"===c.type&&null!=b){for(c=a;c.parentNode;)c=c.parentNode;c=c.querySelectorAll("input[name="+JSON.stringify(""+b)+'][type="radio"]');for(b=0;b<c.length;b++){var d=c[b];if(d!==a&&d.form===a.form){var e=Rc(d);if(!e)throw Error(m(90));jg(d);le(d,e)}}}break;case "textarea":og(a,c);break;case "select":b=c.value,null!=b&&Db(a,!!c.multiple,b,!1)}};(function(a,b,c){xg=a;yg=c})(Tf,function(a,b,c,d,e){var f=z,g=ca.transition;try{return ca.transition=null,z=1,a(b,c,d,e)}finally{z=f,ca.transition=
|
||||
g,0===p&&Hc()}},yb);var ol={usingClientEntryPoint:!1,Events:[ec,Ib,Rc,ug,vg,Tf]};(function(a){a={bundleType:a.bundleType,version:a.version,rendererPackageName:a.rendererPackageName,rendererConfig:a.rendererConfig,overrideHookState:null,overrideHookStateDeletePath:null,overrideHookStateRenamePath:null,overrideProps:null,overridePropsDeletePath:null,overridePropsRenamePath:null,setErrorHandler:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:Sa.ReactCurrentDispatcher,findHostInstanceByFiber:Xk,
|
||||
findFiberByHostInstance:a.findFiberByHostInstance||Yk,findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null,reconcilerVersion:"18.3.1"};if("undefined"===typeof __REACT_DEVTOOLS_GLOBAL_HOOK__)a=!1;else{var b=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(b.isDisabled||!b.supportsFiber)a=!0;else{try{Uc=b.inject(a),Ca=b}catch(c){}a=b.checkDCE?!0:!1}}return a})({findFiberByHostInstance:ob,bundleType:0,version:"18.3.1-next-f1338f8080-20240426",
|
||||
rendererPackageName:"react-dom"});Q.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=ol;Q.createPortal=function(a,b){var c=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;if(!Yf(b))throw Error(m(200));return Wk(a,b,null,c)};Q.createRoot=function(a,b){if(!Yf(a))throw Error(m(299));var c=!1,d="",e=aj;null!==b&&void 0!==b&&(!0===b.unstable_strictMode&&(c=!0),void 0!==b.identifierPrefix&&(d=b.identifierPrefix),void 0!==b.onRecoverableError&&(e=b.onRecoverableError));b=Vf(a,1,!1,null,null,
|
||||
c,!1,d,e);a[Ja]=b.current;sc(8===a.nodeType?a.parentNode:a);return new Xf(b)};Q.findDOMNode=function(a){if(null==a)return null;if(1===a.nodeType)return a;var b=a._reactInternals;if(void 0===b){if("function"===typeof a.render)throw Error(m(188));a=Object.keys(a).join(",");throw Error(m(268,a));}a=Bg(b);a=null===a?null:a.stateNode;return a};Q.flushSync=function(a){return yb(a)};Q.hydrate=function(a,b,c){if(!Vd(b))throw Error(m(200));return Wd(null,a,b,!0,c)};Q.hydrateRoot=function(a,b,c){if(!Yf(a))throw Error(m(405));
|
||||
var d=null!=c&&c.hydratedSources||null,e=!1,f="",g=aj;null!==c&&void 0!==c&&(!0===c.unstable_strictMode&&(e=!0),void 0!==c.identifierPrefix&&(f=c.identifierPrefix),void 0!==c.onRecoverableError&&(g=c.onRecoverableError));b=Wi(b,null,a,1,null!=c?c:null,e,!1,f,g);a[Ja]=b.current;sc(a);if(d)for(a=0;a<d.length;a++)c=d[a],e=c._getVersion,e=e(c._source),null==b.mutableSourceEagerHydrationData?b.mutableSourceEagerHydrationData=[c,e]:b.mutableSourceEagerHydrationData.push(c,e);return new Ud(b)};Q.render=
|
||||
function(a,b,c){if(!Vd(b))throw Error(m(200));return Wd(null,a,b,!1,c)};Q.unmountComponentAtNode=function(a){if(!Vd(a))throw Error(m(40));return a._reactRootContainer?(yb(function(){Wd(null,null,a,!1,function(){a._reactRootContainer=null;a[Ja]=null})}),!0):!1};Q.unstable_batchedUpdates=Tf;Q.unstable_renderSubtreeIntoContainer=function(a,b,c,d){if(!Vd(c))throw Error(m(200));if(null==a||void 0===a._reactInternals)throw Error(m(38));return Wd(a,b,c,!1,d)};Q.version="18.3.1-next-f1338f8080-20240426"});
|
||||
})();
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
(function(){'use strict';(function(c,x){"object"===typeof exports&&"undefined"!==typeof module?x(exports):"function"===typeof define&&define.amd?define(["exports"],x):(c=c||self,x(c.React={}))})(this,function(c){function x(a){if(null===a||"object"!==typeof a)return null;a=V&&a[V]||a["@@iterator"];return"function"===typeof a?a:null}function w(a,b,e){this.props=a;this.context=b;this.refs=W;this.updater=e||X}function Y(){}function K(a,b,e){this.props=a;this.context=b;this.refs=W;this.updater=e||X}function Z(a,b,
|
||||
e){var m,d={},c=null,h=null;if(null!=b)for(m in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(c=""+b.key),b)aa.call(b,m)&&!ba.hasOwnProperty(m)&&(d[m]=b[m]);var l=arguments.length-2;if(1===l)d.children=e;else if(1<l){for(var f=Array(l),k=0;k<l;k++)f[k]=arguments[k+2];d.children=f}if(a&&a.defaultProps)for(m in l=a.defaultProps,l)void 0===d[m]&&(d[m]=l[m]);return{$$typeof:y,type:a,key:c,ref:h,props:d,_owner:L.current}}function oa(a,b){return{$$typeof:y,type:a.type,key:b,ref:a.ref,props:a.props,_owner:a._owner}}
|
||||
function M(a){return"object"===typeof a&&null!==a&&a.$$typeof===y}function pa(a){var b={"=":"=0",":":"=2"};return"$"+a.replace(/[=:]/g,function(a){return b[a]})}function N(a,b){return"object"===typeof a&&null!==a&&null!=a.key?pa(""+a.key):b.toString(36)}function B(a,b,e,m,d){var c=typeof a;if("undefined"===c||"boolean"===c)a=null;var h=!1;if(null===a)h=!0;else switch(c){case "string":case "number":h=!0;break;case "object":switch(a.$$typeof){case y:case qa:h=!0}}if(h)return h=a,d=d(h),a=""===m?"."+
|
||||
N(h,0):m,ca(d)?(e="",null!=a&&(e=a.replace(da,"$&/")+"/"),B(d,b,e,"",function(a){return a})):null!=d&&(M(d)&&(d=oa(d,e+(!d.key||h&&h.key===d.key?"":(""+d.key).replace(da,"$&/")+"/")+a)),b.push(d)),1;h=0;m=""===m?".":m+":";if(ca(a))for(var l=0;l<a.length;l++){c=a[l];var f=m+N(c,l);h+=B(c,b,e,f,d)}else if(f=x(a),"function"===typeof f)for(a=f.call(a),l=0;!(c=a.next()).done;)c=c.value,f=m+N(c,l++),h+=B(c,b,e,f,d);else if("object"===c)throw b=String(a),Error("Objects are not valid as a React child (found: "+
|
||||
("[object Object]"===b?"object with keys {"+Object.keys(a).join(", ")+"}":b)+"). If you meant to render a collection of children, use an array instead.");return h}function C(a,b,e){if(null==a)return a;var c=[],d=0;B(a,c,"","",function(a){return b.call(e,a,d++)});return c}function ra(a){if(-1===a._status){var b=a._result;b=b();b.then(function(b){if(0===a._status||-1===a._status)a._status=1,a._result=b},function(b){if(0===a._status||-1===a._status)a._status=2,a._result=b});-1===a._status&&(a._status=
|
||||
0,a._result=b)}if(1===a._status)return a._result.default;throw a._result;}function O(a,b){var e=a.length;a.push(b);a:for(;0<e;){var c=e-1>>>1,d=a[c];if(0<D(d,b))a[c]=b,a[e]=d,e=c;else break a}}function p(a){return 0===a.length?null:a[0]}function E(a){if(0===a.length)return null;var b=a[0],e=a.pop();if(e!==b){a[0]=e;a:for(var c=0,d=a.length,k=d>>>1;c<k;){var h=2*(c+1)-1,l=a[h],f=h+1,g=a[f];if(0>D(l,e))f<d&&0>D(g,l)?(a[c]=g,a[f]=e,c=f):(a[c]=l,a[h]=e,c=h);else if(f<d&&0>D(g,e))a[c]=g,a[f]=e,c=f;else break a}}return b}
|
||||
function D(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}function P(a){for(var b=p(r);null!==b;){if(null===b.callback)E(r);else if(b.startTime<=a)E(r),b.sortIndex=b.expirationTime,O(q,b);else break;b=p(r)}}function Q(a){z=!1;P(a);if(!u)if(null!==p(q))u=!0,R(S);else{var b=p(r);null!==b&&T(Q,b.startTime-a)}}function S(a,b){u=!1;z&&(z=!1,ea(A),A=-1);F=!0;var c=k;try{P(b);for(n=p(q);null!==n&&(!(n.expirationTime>b)||a&&!fa());){var m=n.callback;if("function"===typeof m){n.callback=null;
|
||||
k=n.priorityLevel;var d=m(n.expirationTime<=b);b=v();"function"===typeof d?n.callback=d:n===p(q)&&E(q);P(b)}else E(q);n=p(q)}if(null!==n)var g=!0;else{var h=p(r);null!==h&&T(Q,h.startTime-b);g=!1}return g}finally{n=null,k=c,F=!1}}function fa(){return v()-ha<ia?!1:!0}function R(a){G=a;H||(H=!0,I())}function T(a,b){A=ja(function(){a(v())},b)}function ka(a){throw Error("act(...) is not supported in production builds of React.");}var y=Symbol.for("react.element"),qa=Symbol.for("react.portal"),sa=Symbol.for("react.fragment"),
|
||||
ta=Symbol.for("react.strict_mode"),ua=Symbol.for("react.profiler"),va=Symbol.for("react.provider"),wa=Symbol.for("react.context"),xa=Symbol.for("react.forward_ref"),ya=Symbol.for("react.suspense"),za=Symbol.for("react.memo"),Aa=Symbol.for("react.lazy"),V=Symbol.iterator,X={isMounted:function(a){return!1},enqueueForceUpdate:function(a,b,c){},enqueueReplaceState:function(a,b,c,m){},enqueueSetState:function(a,b,c,m){}},la=Object.assign,W={};w.prototype.isReactComponent={};w.prototype.setState=function(a,
|
||||
b){if("object"!==typeof a&&"function"!==typeof a&&null!=a)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,a,b,"setState")};w.prototype.forceUpdate=function(a){this.updater.enqueueForceUpdate(this,a,"forceUpdate")};Y.prototype=w.prototype;var t=K.prototype=new Y;t.constructor=K;la(t,w.prototype);t.isPureReactComponent=!0;var ca=Array.isArray,aa=Object.prototype.hasOwnProperty,L={current:null},
|
||||
ba={key:!0,ref:!0,__self:!0,__source:!0},da=/\/+/g,g={current:null},J={transition:null};if("object"===typeof performance&&"function"===typeof performance.now){var Ba=performance;var v=function(){return Ba.now()}}else{var ma=Date,Ca=ma.now();v=function(){return ma.now()-Ca}}var q=[],r=[],Da=1,n=null,k=3,F=!1,u=!1,z=!1,ja="function"===typeof setTimeout?setTimeout:null,ea="function"===typeof clearTimeout?clearTimeout:null,na="undefined"!==typeof setImmediate?setImmediate:null;"undefined"!==typeof navigator&&
|
||||
void 0!==navigator.scheduling&&void 0!==navigator.scheduling.isInputPending&&navigator.scheduling.isInputPending.bind(navigator.scheduling);var H=!1,G=null,A=-1,ia=5,ha=-1,U=function(){if(null!==G){var a=v();ha=a;var b=!0;try{b=G(!0,a)}finally{b?I():(H=!1,G=null)}}else H=!1};if("function"===typeof na)var I=function(){na(U)};else if("undefined"!==typeof MessageChannel){t=new MessageChannel;var Ea=t.port2;t.port1.onmessage=U;I=function(){Ea.postMessage(null)}}else I=function(){ja(U,0)};t={ReactCurrentDispatcher:g,
|
||||
ReactCurrentOwner:L,ReactCurrentBatchConfig:J,Scheduler:{__proto__:null,unstable_ImmediatePriority:1,unstable_UserBlockingPriority:2,unstable_NormalPriority:3,unstable_IdlePriority:5,unstable_LowPriority:4,unstable_runWithPriority:function(a,b){switch(a){case 1:case 2:case 3:case 4:case 5:break;default:a=3}var c=k;k=a;try{return b()}finally{k=c}},unstable_next:function(a){switch(k){case 1:case 2:case 3:var b=3;break;default:b=k}var c=k;k=b;try{return a()}finally{k=c}},unstable_scheduleCallback:function(a,
|
||||
b,c){var e=v();"object"===typeof c&&null!==c?(c=c.delay,c="number"===typeof c&&0<c?e+c:e):c=e;switch(a){case 1:var d=-1;break;case 2:d=250;break;case 5:d=1073741823;break;case 4:d=1E4;break;default:d=5E3}d=c+d;a={id:Da++,callback:b,priorityLevel:a,startTime:c,expirationTime:d,sortIndex:-1};c>e?(a.sortIndex=c,O(r,a),null===p(q)&&a===p(r)&&(z?(ea(A),A=-1):z=!0,T(Q,c-e))):(a.sortIndex=d,O(q,a),u||F||(u=!0,R(S)));return a},unstable_cancelCallback:function(a){a.callback=null},unstable_wrapCallback:function(a){var b=
|
||||
k;return function(){var c=k;k=b;try{return a.apply(this,arguments)}finally{k=c}}},unstable_getCurrentPriorityLevel:function(){return k},unstable_shouldYield:fa,unstable_requestPaint:function(){},unstable_continueExecution:function(){u||F||(u=!0,R(S))},unstable_pauseExecution:function(){},unstable_getFirstCallbackNode:function(){return p(q)},get unstable_now(){return v},unstable_forceFrameRate:function(a){0>a||125<a?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):
|
||||
ia=0<a?Math.floor(1E3/a):5},unstable_Profiling:null}};c.Children={map:C,forEach:function(a,b,c){C(a,function(){b.apply(this,arguments)},c)},count:function(a){var b=0;C(a,function(){b++});return b},toArray:function(a){return C(a,function(a){return a})||[]},only:function(a){if(!M(a))throw Error("React.Children.only expected to receive a single React element child.");return a}};c.Component=w;c.Fragment=sa;c.Profiler=ua;c.PureComponent=K;c.StrictMode=ta;c.Suspense=ya;c.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=
|
||||
t;c.act=ka;c.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+a+".");var e=la({},a.props),d=a.key,k=a.ref,h=a._owner;if(null!=b){void 0!==b.ref&&(k=b.ref,h=L.current);void 0!==b.key&&(d=""+b.key);if(a.type&&a.type.defaultProps)var l=a.type.defaultProps;for(f in b)aa.call(b,f)&&!ba.hasOwnProperty(f)&&(e[f]=void 0===b[f]&&void 0!==l?l[f]:b[f])}var f=arguments.length-2;if(1===f)e.children=c;else if(1<f){l=
|
||||
Array(f);for(var g=0;g<f;g++)l[g]=arguments[g+2];e.children=l}return{$$typeof:y,type:a.type,key:d,ref:k,props:e,_owner:h}};c.createContext=function(a){a={$$typeof:wa,_currentValue:a,_currentValue2:a,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null};a.Provider={$$typeof:va,_context:a};return a.Consumer=a};c.createElement=Z;c.createFactory=function(a){var b=Z.bind(null,a);b.type=a;return b};c.createRef=function(){return{current:null}};c.forwardRef=function(a){return{$$typeof:xa,
|
||||
render:a}};c.isValidElement=M;c.lazy=function(a){return{$$typeof:Aa,_payload:{_status:-1,_result:a},_init:ra}};c.memo=function(a,b){return{$$typeof:za,type:a,compare:void 0===b?null:b}};c.startTransition=function(a,b){b=J.transition;J.transition={};try{a()}finally{J.transition=b}};c.unstable_act=ka;c.useCallback=function(a,b){return g.current.useCallback(a,b)};c.useContext=function(a){return g.current.useContext(a)};c.useDebugValue=function(a,b){};c.useDeferredValue=function(a){return g.current.useDeferredValue(a)};
|
||||
c.useEffect=function(a,b){return g.current.useEffect(a,b)};c.useId=function(){return g.current.useId()};c.useImperativeHandle=function(a,b,c){return g.current.useImperativeHandle(a,b,c)};c.useInsertionEffect=function(a,b){return g.current.useInsertionEffect(a,b)};c.useLayoutEffect=function(a,b){return g.current.useLayoutEffect(a,b)};c.useMemo=function(a,b){return g.current.useMemo(a,b)};c.useReducer=function(a,b,c){return g.current.useReducer(a,b,c)};c.useRef=function(a){return g.current.useRef(a)};
|
||||
c.useState=function(a){return g.current.useState(a)};c.useSyncExternalStore=function(a,b,c){return g.current.useSyncExternalStore(a,b,c)};c.useTransition=function(){return g.current.useTransition()};c.version="18.3.1"});
|
||||
})();
|
||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 19 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "SecureBit.chat - Enhanced Security Edition",
|
||||
"name": "SecureBit.chat v4.7.56 - ECDH + DTLS + SAS",
|
||||
"short_name": "SecureBit",
|
||||
"description": "P2P messenger with military-grade cryptography and Lightning Network payments",
|
||||
"description": "P2P messenger with ECDH + DTLS + SAS security, military-grade cryptography and Lightning Network payments",
|
||||
"start_url": "./",
|
||||
"display": "standalone",
|
||||
"background_color": "#1a1a1a",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": "1779043608721",
|
||||
"buildVersion": "1779043608721",
|
||||
"appVersion": "4.8.5",
|
||||
"buildTime": "2026-05-17T18:46:48.763Z",
|
||||
"buildId": "1779043608721-4b8c882",
|
||||
"gitHash": "4b8c882",
|
||||
"generated": true,
|
||||
"generatedAt": "2026-05-17T18:46:48.764Z"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "securebit-chat",
|
||||
"version": "4.8.5",
|
||||
"description": "Secure P2P Communication Application with End-to-End Encryption",
|
||||
"main": "index.html",
|
||||
"scripts": {
|
||||
"build": "npm run build:css && npm run build:js && npm run post-build",
|
||||
"build:css": "npx tailwindcss -i src/styles/tw-input.css -o assets/tailwind.css --minify --content \"./index.html,./src/**/*.jsx,./src/**/*.js\"",
|
||||
"build:js": "npx esbuild src/app.jsx --bundle --format=esm --outfile=dist/app.js --sourcemap && npx esbuild src/scripts/app-boot.js --bundle --format=esm --outfile=dist/app-boot.js --sourcemap && npx esbuild src/scripts/qr-local.js --bundle --format=esm --outfile=dist/qr-local.js --sourcemap",
|
||||
"post-build": "node scripts/post-build.js",
|
||||
"dev": "npm run build && python -m http.server 8000",
|
||||
"watch": "npx tailwindcss -i src/styles/tw-input.css -o assets/tailwind.css --watch",
|
||||
"serve": "npx http-server -p 8000",
|
||||
"test": "node tests/sas-verification.test.mjs && node tests/file-transfer-consent.test.mjs && node tests/incoming-message-sanitization.test.mjs && node tests/file-type-allowlist.test.mjs && node tests/webrtc-privacy-mode.test.mjs && node tests/indexeddb-metadata-encryption.test.mjs && node tests/disconnect-cleanup.test.mjs && node tests/timer-lifecycle.test.mjs && node tests/file-transfer-cleanup.test.mjs && node tests/file-transfer-ui-cleanup.test.mjs && node tests/file-transfer-callback-propagation.test.mjs"
|
||||
},
|
||||
"keywords": [
|
||||
"p2p",
|
||||
"chat",
|
||||
"encryption",
|
||||
"webrtc",
|
||||
"privacy",
|
||||
"security"
|
||||
],
|
||||
"author": "SecureBit Team",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"esbuild": "0.25.9",
|
||||
"jsdom": "^28.1.0",
|
||||
"tailwindcss": "3.4.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"base64-js": "1.5.1",
|
||||
"cbor-js": "0.1.0",
|
||||
"dompurify": "^3.4.4",
|
||||
"html5-qrcode": "2.3.8",
|
||||
"pako": "2.1.0",
|
||||
"qr-scanner": "1.4.2",
|
||||
"qrcode": "1.5.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "1735689600000",
|
||||
"buildVersion": "1735689600000",
|
||||
"appVersion": "1.0.0",
|
||||
"buildTime": "2025-01-01T00:00:00.000Z",
|
||||
"buildId": "1735689600000",
|
||||
"gitHash": null,
|
||||
"generated": true,
|
||||
"generatedAt": "2025-01-01T00:00:00.000Z"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* post-build.js - Script for generating meta.json after build
|
||||
*
|
||||
* Generates meta.json file with unique build version (timestamp)
|
||||
* for automatic update detection
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Configuration
|
||||
const CONFIG = {
|
||||
// Path to public directory (project root where index.html is located)
|
||||
publicDir: path.join(__dirname, '..'),
|
||||
|
||||
// meta.json filename
|
||||
metaFileName: 'meta.json',
|
||||
|
||||
// Version format: 'timestamp' or 'semver'
|
||||
versionFormat: 'timestamp'
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate unique build version
|
||||
*/
|
||||
function generateBuildVersion() {
|
||||
// Use timestamp for uniqueness of each build
|
||||
const timestamp = Date.now();
|
||||
|
||||
// Optional: can add git commit hash
|
||||
let gitHash = '';
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
gitHash = execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim();
|
||||
} catch (error) {
|
||||
// Git not available or not initialized - ignore
|
||||
}
|
||||
|
||||
return {
|
||||
version: timestamp.toString(),
|
||||
buildTime: new Date().toISOString(),
|
||||
gitHash: gitHash || null,
|
||||
buildId: `${timestamp}${gitHash ? `-${gitHash}` : ''}`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Read package.json to get application version
|
||||
*/
|
||||
function getAppVersion() {
|
||||
try {
|
||||
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||
return packageJson.version || '1.0.0';
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Failed to read package.json:', error.message);
|
||||
}
|
||||
return '1.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate meta.json
|
||||
*/
|
||||
function generateMetaJson() {
|
||||
try {
|
||||
const buildInfo = generateBuildVersion();
|
||||
const appVersion = getAppVersion();
|
||||
|
||||
const meta = {
|
||||
// Build version (used for update checking)
|
||||
version: buildInfo.version,
|
||||
buildVersion: buildInfo.version,
|
||||
|
||||
// Application version from package.json
|
||||
appVersion: appVersion,
|
||||
|
||||
// Additional information
|
||||
buildTime: buildInfo.buildTime,
|
||||
buildId: buildInfo.buildId,
|
||||
gitHash: buildInfo.gitHash,
|
||||
|
||||
// Metadata
|
||||
generated: true,
|
||||
generatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Path to meta.json file (in project root where index.html is located)
|
||||
const metaFilePath = path.join(CONFIG.publicDir, CONFIG.metaFileName);
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
const publicDir = path.dirname(metaFilePath);
|
||||
if (!fs.existsSync(publicDir)) {
|
||||
fs.mkdirSync(publicDir, { recursive: true });
|
||||
console.log(`✅ Created directory: ${publicDir}`);
|
||||
}
|
||||
|
||||
// Write meta.json
|
||||
fs.writeFileSync(
|
||||
metaFilePath,
|
||||
JSON.stringify(meta, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
console.log('✅ meta.json generated successfully');
|
||||
console.log(` Version: ${meta.version}`);
|
||||
console.log(` Build Time: ${meta.buildTime}`);
|
||||
if (meta.gitHash) {
|
||||
console.log(` Git Hash: ${meta.gitHash}`);
|
||||
}
|
||||
console.log(` File: ${metaFilePath}`);
|
||||
|
||||
return meta;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to generate meta.json:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update versions in index.html
|
||||
*/
|
||||
function updateIndexHtmlVersions(buildVersion) {
|
||||
try {
|
||||
const indexHtmlPath = path.join(CONFIG.publicDir, 'index.html');
|
||||
|
||||
if (!fs.existsSync(indexHtmlPath)) {
|
||||
console.warn('⚠️ index.html not found, skipping version update');
|
||||
return;
|
||||
}
|
||||
|
||||
let indexHtml = fs.readFileSync(indexHtmlPath, 'utf-8');
|
||||
|
||||
// Update versions in query parameters for JS files
|
||||
// Pattern: src="dist/app.js?v=..." or src="dist/app-boot.js?v=..."
|
||||
// Also replace BUILD_VERSION placeholder
|
||||
indexHtml = indexHtml.replace(/\?v=BUILD_VERSION/g, `?v=${buildVersion}`);
|
||||
indexHtml = indexHtml.replace(/\?v=(\d+)/g, `?v=${buildVersion}`);
|
||||
|
||||
fs.writeFileSync(indexHtmlPath, indexHtml, 'utf-8');
|
||||
console.log('✅ index.html versions updated');
|
||||
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Failed to update index.html versions:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate generated meta.json
|
||||
*/
|
||||
function validateMetaJson(meta) {
|
||||
const requiredFields = ['version', 'buildVersion', 'buildTime'];
|
||||
const missingFields = requiredFields.filter(field => !meta[field]);
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
throw new Error(`Missing required fields: ${missingFields.join(', ')}`);
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(meta.version)) {
|
||||
throw new Error(`Invalid version format: ${meta.version} (expected timestamp)`);
|
||||
}
|
||||
|
||||
console.log('✅ meta.json validation passed');
|
||||
}
|
||||
|
||||
// Main function
|
||||
function main() {
|
||||
console.log('🔨 Generating meta.json...');
|
||||
console.log(` Public directory: ${CONFIG.publicDir}`);
|
||||
|
||||
// Check if public directory exists
|
||||
if (!fs.existsSync(CONFIG.publicDir)) {
|
||||
console.error(`❌ Public directory not found: ${CONFIG.publicDir}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Generate meta.json
|
||||
const meta = generateMetaJson();
|
||||
|
||||
// Validate
|
||||
validateMetaJson(meta);
|
||||
|
||||
// Update versions in index.html
|
||||
updateIndexHtmlVersions(meta.version);
|
||||
|
||||
console.log('✅ Build metadata generation completed');
|
||||
}
|
||||
|
||||
// Run script
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
// Export for use in other scripts
|
||||
module.exports = {
|
||||
generateMetaJson,
|
||||
generateBuildVersion,
|
||||
getAppVersion,
|
||||
validateMetaJson
|
||||
};
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Скрипт для очистки кеша Cloudflare после деплоя
|
||||
*
|
||||
* Использование:
|
||||
* CLOUDFLARE_API_TOKEN=your_token CLOUDFLARE_ZONE_ID=your_zone_id node scripts/purge-cloudflare-cache.js
|
||||
*/
|
||||
|
||||
const https = require('https');
|
||||
|
||||
const API_TOKEN = process.env.CLOUDFLARE_API_TOKEN;
|
||||
const ZONE_ID = process.env.CLOUDFLARE_ZONE_ID;
|
||||
const DOMAIN = process.env.CLOUDFLARE_DOMAIN || 'securebit.chat';
|
||||
|
||||
if (!API_TOKEN || !ZONE_ID) {
|
||||
console.error('❌ Missing required environment variables:');
|
||||
console.error(' CLOUDFLARE_API_TOKEN - Cloudflare API Token');
|
||||
console.error(' CLOUDFLARE_ZONE_ID - Cloudflare Zone ID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Критичные файлы для очистки
|
||||
const CRITICAL_FILES = [
|
||||
`https://${DOMAIN}/meta.json`,
|
||||
`https://${DOMAIN}/index.html`,
|
||||
`https://${DOMAIN}/sw.js`,
|
||||
`https://${DOMAIN}/manifest.json`
|
||||
];
|
||||
|
||||
async function purgeCache(files) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = JSON.stringify({
|
||||
files: files
|
||||
});
|
||||
|
||||
const options = {
|
||||
hostname: 'api.cloudflare.com',
|
||||
port: 443,
|
||||
path: `/client/v4/zones/${ZONE_ID}/purge_cache`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': data.length
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let responseData = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
responseData += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
if (res.statusCode === 200) {
|
||||
const result = JSON.parse(responseData);
|
||||
if (result.success) {
|
||||
resolve(result);
|
||||
} else {
|
||||
reject(new Error(JSON.stringify(result.errors)));
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`HTTP ${res.statusCode}: ${responseData}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
req.write(data);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🔄 Purging Cloudflare cache...');
|
||||
console.log(` Zone ID: ${ZONE_ID}`);
|
||||
console.log(` Domain: ${DOMAIN}`);
|
||||
console.log(` Files: ${CRITICAL_FILES.length}`);
|
||||
|
||||
try {
|
||||
const result = await purgeCache(CRITICAL_FILES);
|
||||
console.log('✅ Cache purged successfully');
|
||||
console.log(` Purged files: ${result.result.files?.length || 0}`);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to purge cache:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -0,0 +1,492 @@
|
||||
// Simple QR Scanner Component using only Html5Qrcode
|
||||
const QRScanner = ({ onScan, onClose, isVisible, continuous = false }) => {
|
||||
const videoRef = React.useRef(null);
|
||||
const qrScannerRef = React.useRef(null);
|
||||
const [error, setError] = React.useState(null);
|
||||
const [isScanning, setIsScanning] = React.useState(false);
|
||||
const [progress, setProgress] = React.useState({ id: null, seq: 0, total: 0 });
|
||||
const [showFocusHint, setShowFocusHint] = React.useState(false);
|
||||
const [manualMode, setManualMode] = React.useState(false);
|
||||
const [scannedParts, setScannedParts] = React.useState(new Set());
|
||||
const [currentQRId, setCurrentQRId] = React.useState(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isVisible) {
|
||||
startScanner();
|
||||
} else {
|
||||
stopScanner();
|
||||
}
|
||||
|
||||
return () => {
|
||||
stopScanner();
|
||||
};
|
||||
}, [isVisible]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const onProgress = (e) => {
|
||||
const { id, seq, total } = e.detail || {};
|
||||
if (!id || !total) return;
|
||||
setProgress({ id, seq, total });
|
||||
|
||||
// Обновляем ID текущего QR кода
|
||||
if (id !== currentQRId) {
|
||||
setCurrentQRId(id);
|
||||
setScannedParts(new Set()); // Сбрасываем сканированные части для нового ID
|
||||
}
|
||||
|
||||
// Добавляем отсканированную часть
|
||||
setScannedParts(prev => new Set([...prev, seq]));
|
||||
};
|
||||
const onComplete = () => {
|
||||
// Close scanner once app signals completion
|
||||
if (!continuous) return;
|
||||
try { stopScanner(); } catch {}
|
||||
};
|
||||
document.addEventListener('qr-scan-progress', onProgress, { passive: true });
|
||||
document.addEventListener('qr-scan-complete', onComplete, { passive: true });
|
||||
return () => {
|
||||
document.removeEventListener('qr-scan-progress', onProgress, { passive: true });
|
||||
document.removeEventListener('qr-scan-complete', onComplete, { passive: true });
|
||||
};
|
||||
}, [currentQRId]);
|
||||
|
||||
// Функция для tap-to-focus
|
||||
const handleTapToFocus = (event, html5Qrcode) => {
|
||||
try {
|
||||
// Показываем подсказку о фокусировке
|
||||
setShowFocusHint(true);
|
||||
setTimeout(() => setShowFocusHint(false), 2000);
|
||||
|
||||
// Получаем координаты клика относительно видео элемента
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const y = event.clientY - rect.top;
|
||||
|
||||
// Нормализуем координаты (0-1)
|
||||
const normalizedX = x / rect.width;
|
||||
const normalizedY = y / rect.height;
|
||||
|
||||
console.log('Tap to focus at:', { x, y, normalizedX, normalizedY });
|
||||
|
||||
// Попытка программной фокусировки (если поддерживается браузером)
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
// Это может не работать во всех браузерах, но попробуем
|
||||
console.log('Attempting programmatic focus...');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Tap to focus error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Функции для ручного управления
|
||||
const toggleManualMode = () => {
|
||||
setManualMode(!manualMode);
|
||||
if (!manualMode) {
|
||||
// При включении ручного режима останавливаем автопрокрутку
|
||||
console.log('Manual mode enabled - auto-scroll disabled');
|
||||
} else {
|
||||
// При выключении ручного режима возобновляем автопрокрутку
|
||||
console.log('Manual mode disabled - auto-scroll enabled');
|
||||
}
|
||||
};
|
||||
|
||||
const resetProgress = () => {
|
||||
setScannedParts(new Set());
|
||||
setCurrentQRId(null);
|
||||
setProgress({ id: null, seq: 0, total: 0 });
|
||||
};
|
||||
|
||||
const startScanner = async () => {
|
||||
try {
|
||||
console.log('Starting QR scanner...');
|
||||
setError(null);
|
||||
setIsScanning(true);
|
||||
|
||||
// Allow camera on HTTP as well; rely on browser permission prompts
|
||||
|
||||
// Check if Html5Qrcode is available
|
||||
if (!window.Html5Qrcode) {
|
||||
setError('QR scanner library not loaded');
|
||||
setIsScanning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get available cameras first
|
||||
console.log('Getting available cameras...');
|
||||
const cameras = await window.Html5Qrcode.getCameras();
|
||||
console.log('Available cameras:', cameras);
|
||||
|
||||
if (!cameras || cameras.length === 0) {
|
||||
setError('No cameras found on this device');
|
||||
setIsScanning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any existing scanner
|
||||
if (qrScannerRef.current) {
|
||||
try {
|
||||
qrScannerRef.current.stop();
|
||||
} catch (e) {
|
||||
console.log('Stopping previous scanner:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Create video element if it doesn't exist
|
||||
if (!videoRef.current) {
|
||||
console.log('Video element not found');
|
||||
setError('Video element not found');
|
||||
setIsScanning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Video element found:', videoRef.current);
|
||||
console.log('Video element ID:', videoRef.current.id);
|
||||
|
||||
// Create Html5Qrcode instance
|
||||
console.log('Creating Html5Qrcode instance...');
|
||||
const html5Qrcode = new window.Html5Qrcode(videoRef.current.id || 'qr-reader');
|
||||
|
||||
// Find back camera (environment facing)
|
||||
let cameraId = cameras[0].id; // Default to first camera
|
||||
let selectedCamera = cameras[0];
|
||||
|
||||
// Look for back camera
|
||||
for (const camera of cameras) {
|
||||
if (camera.label.toLowerCase().includes('back') ||
|
||||
camera.label.toLowerCase().includes('rear') ||
|
||||
camera.label.toLowerCase().includes('environment')) {
|
||||
cameraId = camera.id;
|
||||
selectedCamera = camera;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Available cameras:');
|
||||
cameras.forEach((cam, index) => {
|
||||
console.log(`${index + 1}. ${cam.label} (${cam.id})`);
|
||||
});
|
||||
console.log('Selected camera:', selectedCamera.label, 'ID:', cameraId);
|
||||
|
||||
// Start camera
|
||||
console.log('Starting camera with Html5Qrcode...');
|
||||
const isDesktop = (typeof window !== 'undefined') && ((window.innerWidth || 0) >= 1024);
|
||||
const qrboxSize = isDesktop ? 560 : 360;
|
||||
await html5Qrcode.start(
|
||||
cameraId, // Use specific camera ID
|
||||
{
|
||||
fps: /iPhone|iPad|iPod/i.test(navigator.userAgent) ? 2 : 3,
|
||||
qrbox: { width: qrboxSize, height: qrboxSize },
|
||||
// Улучшенные настройки для мобильных устройств
|
||||
aspectRatio: 1.0,
|
||||
videoConstraints: {
|
||||
focusMode: "continuous", // Непрерывная автофокусировка
|
||||
exposureMode: "continuous", // Непрерывная экспозиция
|
||||
whiteBalanceMode: "continuous", // Непрерывный баланс белого
|
||||
torch: false, // Вспышка выключена по умолчанию
|
||||
facingMode: "environment" // Используем заднюю камеру
|
||||
}
|
||||
},
|
||||
(decodedText, decodedResult) => {
|
||||
console.log('QR Code detected:', decodedText);
|
||||
try {
|
||||
const res = onScan(decodedText);
|
||||
const handleResult = (val) => {
|
||||
const shouldClose = val === true || !continuous;
|
||||
if (shouldClose) {
|
||||
stopScanner();
|
||||
}
|
||||
};
|
||||
if (res && typeof res.then === 'function') {
|
||||
res.then(handleResult).catch((e) => {
|
||||
console.warn('onScan async handler error:', e);
|
||||
if (!continuous) stopScanner();
|
||||
});
|
||||
} else {
|
||||
handleResult(res);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('onScan handler threw:', e);
|
||||
if (!continuous) {
|
||||
stopScanner();
|
||||
}
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
// Ignore decode errors, they're normal during scanning
|
||||
console.log('QR decode error:', error);
|
||||
}
|
||||
);
|
||||
|
||||
// Store scanner reference
|
||||
qrScannerRef.current = html5Qrcode;
|
||||
console.log('QR scanner started successfully');
|
||||
|
||||
// Добавляем обработчик tap-to-focus для мобильных устройств
|
||||
if (videoRef.current) {
|
||||
videoRef.current.addEventListener('click', (event) => {
|
||||
handleTapToFocus(event, html5Qrcode);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error starting QR scanner:', err);
|
||||
let errorMessage = 'Failed to start camera';
|
||||
|
||||
if (err.name === 'NotAllowedError') {
|
||||
errorMessage = 'Camera access denied. Please allow camera access and try again.';
|
||||
} else if (err.name === 'NotFoundError') {
|
||||
errorMessage = 'No camera found on this device.';
|
||||
} else if (err.name === 'NotSupportedError') {
|
||||
errorMessage = 'Camera not supported on this device.';
|
||||
} else if (err.name === 'NotReadableError') {
|
||||
errorMessage = 'Camera is already in use by another application.';
|
||||
} else if (err.message) {
|
||||
errorMessage = err.message;
|
||||
}
|
||||
|
||||
setError(errorMessage);
|
||||
setIsScanning(false);
|
||||
}
|
||||
};
|
||||
|
||||
const stopScanner = () => {
|
||||
if (qrScannerRef.current) {
|
||||
try {
|
||||
qrScannerRef.current.stop().then(() => {
|
||||
console.log('QR scanner stopped');
|
||||
}).catch((err) => {
|
||||
console.log('Error stopping scanner:', err);
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('Error stopping scanner:', err);
|
||||
}
|
||||
qrScannerRef.current = null;
|
||||
}
|
||||
setIsScanning(false);
|
||||
try {
|
||||
// iOS Safari workaround: small delay before closing modal to release camera
|
||||
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||
setTimeout(() => {
|
||||
// no-op; allow camera to settle
|
||||
}, 150);
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
stopScanner();
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.createElement('div', {
|
||||
className: "fixed inset-0 bg-black/80 flex items-center justify-center z-50"
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'scanner-modal',
|
||||
className: "bg-gray-800 rounded-lg p-6 w-full mx-4 max-w-2xl"
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'scanner-header',
|
||||
className: "flex items-center justify-between mb-4"
|
||||
}, [
|
||||
React.createElement('h3', {
|
||||
key: 'title',
|
||||
className: "text-lg font-medium text-white"
|
||||
}, 'Scan QR Code'),
|
||||
React.createElement('button', {
|
||||
key: 'close-btn',
|
||||
onClick: handleClose,
|
||||
className: "text-gray-400 hover:text-white transition-colors"
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
className: 'fas fa-times text-xl'
|
||||
})
|
||||
])
|
||||
]),
|
||||
|
||||
// Индикатор прогресса сканирования
|
||||
progress.total > 1 && React.createElement('div', {
|
||||
key: 'progress-indicator',
|
||||
className: "mb-4 p-3 bg-gray-800/50 border border-gray-600/30 rounded-lg"
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'progress-header',
|
||||
className: "flex items-center justify-between mb-2"
|
||||
}, [
|
||||
React.createElement('span', {
|
||||
key: 'progress-title',
|
||||
className: "text-sm text-gray-300"
|
||||
}, `QR ID: ${currentQRId ? currentQRId.substring(0, 8) + '...' : 'N/A'}`),
|
||||
React.createElement('span', {
|
||||
key: 'progress-count',
|
||||
className: "text-sm text-blue-400"
|
||||
}, `${scannedParts.size}/${progress.total} scanned`)
|
||||
]),
|
||||
React.createElement('div', {
|
||||
key: 'progress-numbers',
|
||||
className: "flex flex-wrap gap-1"
|
||||
}, Array.from({ length: progress.total }, (_, i) => {
|
||||
const partNumber = i + 1;
|
||||
const isScanned = scannedParts.has(partNumber);
|
||||
return React.createElement('div', {
|
||||
key: `part-${partNumber}`,
|
||||
className: `w-8 h-8 rounded-full flex items-center justify-center text-xs font-medium transition-colors ${
|
||||
isScanned
|
||||
? 'bg-green-500 text-white'
|
||||
: 'bg-gray-600 text-gray-300'
|
||||
}`
|
||||
}, partNumber);
|
||||
}))
|
||||
]),
|
||||
|
||||
// Панель управления
|
||||
progress.total > 1 && React.createElement('div', {
|
||||
key: 'control-panel',
|
||||
className: "mb-4 flex gap-2"
|
||||
}, [
|
||||
React.createElement('button', {
|
||||
key: 'manual-toggle',
|
||||
onClick: toggleManualMode,
|
||||
className: `px-3 py-1 rounded text-xs font-medium transition-colors ${
|
||||
manualMode
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-600 text-gray-300 hover:bg-gray-500'
|
||||
}`
|
||||
}, manualMode ? 'Manual Mode' : 'Auto Mode'),
|
||||
React.createElement('button', {
|
||||
key: 'reset-progress',
|
||||
onClick: resetProgress,
|
||||
className: "px-3 py-1 bg-red-500/20 text-red-400 border border-red-500/20 rounded text-xs font-medium hover:bg-red-500/30"
|
||||
}, 'Reset'),
|
||||
React.createElement('span', {
|
||||
key: 'mode-hint',
|
||||
className: "text-xs text-gray-400 self-center"
|
||||
}, manualMode ? 'Tap to focus, scan manually' : 'Auto-scrolling enabled')
|
||||
]),
|
||||
|
||||
React.createElement('div', {
|
||||
key: 'scanner-content',
|
||||
className: "relative"
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'video-container',
|
||||
id: 'qr-reader',
|
||||
ref: videoRef,
|
||||
className: "w-full h-80 md:h-[32rem] bg-gray-700 rounded-lg"
|
||||
}),
|
||||
|
||||
error && React.createElement('div', {
|
||||
key: 'error',
|
||||
className: "absolute inset-0 flex items-center justify-center bg-red-900/50 rounded-lg"
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'error-content',
|
||||
className: "text-center text-white p-4"
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'error-icon',
|
||||
className: 'fas fa-exclamation-triangle text-2xl mb-2'
|
||||
}),
|
||||
React.createElement('p', {
|
||||
key: 'error-text',
|
||||
className: "text-sm"
|
||||
}, error)
|
||||
])
|
||||
]),
|
||||
|
||||
!error && !isScanning && React.createElement('div', {
|
||||
key: 'loading',
|
||||
className: "absolute inset-0 flex items-center justify-center bg-gray-700/50 rounded-lg"
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'loading-content',
|
||||
className: "text-center text-white"
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'loading-icon',
|
||||
className: 'fas fa-spinner fa-spin text-2xl mb-2'
|
||||
}),
|
||||
React.createElement('p', {
|
||||
key: 'loading-text',
|
||||
className: "text-sm"
|
||||
}, 'Starting camera...')
|
||||
])
|
||||
]),
|
||||
|
||||
!error && isScanning && React.createElement('div', {
|
||||
key: 'scanning-overlay',
|
||||
className: "absolute inset-0 flex items-center justify-center"
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'scanning-content',
|
||||
className: "text-center text-white bg-black/50 rounded-lg px-4 py-2"
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'scanning-icon',
|
||||
className: 'fas fa-qrcode text-xl mb-1'
|
||||
}),
|
||||
React.createElement('p', {
|
||||
key: 'scanning-text',
|
||||
className: "text-xs"
|
||||
}, progress && progress.total > 1 ? `Frames: ${Math.min(progress.seq, progress.total)}/${progress.total}` : 'Point camera at QR code'),
|
||||
React.createElement('p', {
|
||||
key: 'tap-hint',
|
||||
className: "text-xs text-blue-300 mt-1"
|
||||
}, 'Tap screen to focus')
|
||||
])
|
||||
]),
|
||||
|
||||
// Подсказка о фокусировке
|
||||
showFocusHint && React.createElement('div', {
|
||||
key: 'focus-hint',
|
||||
className: "absolute top-4 left-1/2 transform -translate-x-1/2 bg-green-500/90 text-white px-3 py-1 rounded-full text-xs font-medium z-10"
|
||||
}, 'Focusing...'),
|
||||
// Bottom overlay kept simple on mobile
|
||||
]),
|
||||
|
||||
// Дополнительные подсказки для улучшения сканирования
|
||||
React.createElement('div', {
|
||||
key: 'scanning-tips',
|
||||
className: "mt-4 p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg"
|
||||
}, [
|
||||
React.createElement('h4', {
|
||||
key: 'tips-title',
|
||||
className: "text-blue-400 text-sm font-medium mb-2 flex items-center"
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'tips-icon',
|
||||
className: 'fas fa-lightbulb mr-2'
|
||||
}),
|
||||
'Tips for better scanning:'
|
||||
]),
|
||||
React.createElement('ul', {
|
||||
key: 'tips-list',
|
||||
className: "text-xs text-blue-300 space-y-1"
|
||||
}, [
|
||||
React.createElement('li', {
|
||||
key: 'tip-1'
|
||||
}, '• Ensure good lighting'),
|
||||
React.createElement('li', {
|
||||
key: 'tip-2'
|
||||
}, '• Hold phone steady'),
|
||||
React.createElement('li', {
|
||||
key: 'tip-3'
|
||||
}, '• Tap screen to focus'),
|
||||
React.createElement('li', {
|
||||
key: 'tip-4'
|
||||
}, '• Keep QR code in frame')
|
||||
])
|
||||
])
|
||||
|
||||
])
|
||||
]);
|
||||
};
|
||||
|
||||
// Export for use in other files
|
||||
window.QRScanner = QRScanner;
|
||||
console.log('QRScanner component loaded and available on window.QRScanner');
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
/**
|
||||
* UpdateChecker - React component for automatic update checking
|
||||
*
|
||||
* Wraps the application and automatically detects new versions,
|
||||
* showing a modal window with update progress
|
||||
*/
|
||||
|
||||
const UpdateChecker = ({ children, onUpdateAvailable, debug = false }) => {
|
||||
const [updateState, setUpdateState] = React.useState({
|
||||
hasUpdate: false,
|
||||
isUpdating: false,
|
||||
progress: 0,
|
||||
currentVersion: null,
|
||||
newVersion: null,
|
||||
showModal: false
|
||||
});
|
||||
|
||||
const updateManagerRef = React.useRef(null);
|
||||
|
||||
// Initialize UpdateManager
|
||||
React.useEffect(() => {
|
||||
// Check that UpdateManager is available
|
||||
if (typeof window === 'undefined' || !window.UpdateManager) {
|
||||
console.error('❌ UpdateManager not found. Make sure updateManager.js is loaded.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create UpdateManager instance
|
||||
updateManagerRef.current = new window.UpdateManager({
|
||||
versionUrl: '/meta.json',
|
||||
checkInterval: 60000, // 1 minute
|
||||
checkOnLoad: true,
|
||||
debug: debug,
|
||||
onUpdateAvailable: (updateInfo) => {
|
||||
setUpdateState(prev => ({
|
||||
...prev,
|
||||
hasUpdate: true,
|
||||
currentVersion: updateInfo.currentVersion,
|
||||
newVersion: updateInfo.newVersion,
|
||||
showModal: true
|
||||
}));
|
||||
|
||||
// Call external callback if available
|
||||
if (onUpdateAvailable) {
|
||||
onUpdateAvailable(updateInfo);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
if (debug) {
|
||||
console.warn('Update check error (non-critical):', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup on unmount
|
||||
return () => {
|
||||
if (updateManagerRef.current) {
|
||||
updateManagerRef.current.destroy();
|
||||
}
|
||||
};
|
||||
}, [onUpdateAvailable, debug]);
|
||||
|
||||
// Force update handler
|
||||
const handleForceUpdate = async () => {
|
||||
if (!updateManagerRef.current || updateState.isUpdating) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUpdateState(prev => ({
|
||||
...prev,
|
||||
isUpdating: true,
|
||||
progress: 0
|
||||
}));
|
||||
|
||||
try {
|
||||
// Simulate update progress
|
||||
const progressSteps = [
|
||||
{ progress: 10, message: 'Saving data...' },
|
||||
{ progress: 30, message: 'Clearing Service Worker caches...' },
|
||||
{ progress: 50, message: 'Unregistering Service Workers...' },
|
||||
{ progress: 70, message: 'Clearing browser cache...' },
|
||||
{ progress: 90, message: 'Updating version...' },
|
||||
{ progress: 100, message: 'Reloading application...' }
|
||||
];
|
||||
|
||||
for (const step of progressSteps) {
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
setUpdateState(prev => ({
|
||||
...prev,
|
||||
progress: step.progress
|
||||
}));
|
||||
}
|
||||
|
||||
// Start force update
|
||||
await updateManagerRef.current.forceUpdate();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Update failed:', error);
|
||||
setUpdateState(prev => ({
|
||||
...prev,
|
||||
isUpdating: false,
|
||||
progress: 0
|
||||
}));
|
||||
|
||||
// Show error to user
|
||||
alert('Update error. Please refresh the page manually (Ctrl+F5 or Cmd+Shift+R)');
|
||||
}
|
||||
};
|
||||
|
||||
// Close modal (not recommended, but leaving the option)
|
||||
const handleCloseModal = () => {
|
||||
// Warn user
|
||||
if (window.confirm('New version available. Update is recommended for security and stability. Continue without update?')) {
|
||||
setUpdateState(prev => ({
|
||||
...prev,
|
||||
showModal: false
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// Format version for display
|
||||
const formatVersion = (version) => {
|
||||
if (!version) return 'N/A';
|
||||
// If version is timestamp, format as date
|
||||
if (/^\d+$/.test(version)) {
|
||||
const date = new Date(parseInt(version));
|
||||
return date.toLocaleString('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
return version;
|
||||
};
|
||||
|
||||
return React.createElement(React.Fragment, null, [
|
||||
// Main application content
|
||||
children,
|
||||
|
||||
// Update modal window
|
||||
updateState.showModal && React.createElement('div', {
|
||||
key: 'update-modal',
|
||||
className: 'fixed inset-0 z-[9999] flex items-center justify-center bg-black/80 backdrop-blur-sm',
|
||||
style: {
|
||||
animation: 'fadeIn 0.3s ease-in-out'
|
||||
}
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'modal-content',
|
||||
className: 'bg-white dark:bg-gray-800 rounded-2xl shadow-2xl p-8 max-w-md w-full mx-4 border border-gray-200 dark:border-gray-700',
|
||||
style: {
|
||||
animation: 'slideUp 0.3s ease-out'
|
||||
}
|
||||
}, [
|
||||
// Header
|
||||
React.createElement('div', {
|
||||
key: 'header',
|
||||
className: 'text-center mb-6'
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'icon',
|
||||
className: 'w-16 h-16 mx-auto mb-4 bg-blue-500/10 rounded-full flex items-center justify-center'
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'icon-fa',
|
||||
className: 'fas fa-sync-alt text-blue-500 text-2xl animate-spin'
|
||||
})
|
||||
]),
|
||||
React.createElement('h2', {
|
||||
key: 'title',
|
||||
className: 'text-2xl font-bold text-gray-900 dark:text-white mb-2'
|
||||
}, 'Update Available'),
|
||||
React.createElement('p', {
|
||||
key: 'subtitle',
|
||||
className: 'text-gray-600 dark:text-gray-300 text-sm'
|
||||
}, 'A new version of the application has been detected')
|
||||
]),
|
||||
|
||||
// Version information
|
||||
React.createElement('div', {
|
||||
key: 'version-info',
|
||||
className: 'bg-gray-50 dark:bg-gray-900 rounded-lg p-4 mb-6 space-y-2'
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'current',
|
||||
className: 'flex justify-between items-center'
|
||||
}, [
|
||||
React.createElement('span', {
|
||||
key: 'current-label',
|
||||
className: 'text-sm text-gray-600 dark:text-gray-400'
|
||||
}, 'Current version:'),
|
||||
React.createElement('span', {
|
||||
key: 'current-value',
|
||||
className: 'text-sm font-mono text-gray-900 dark:text-white'
|
||||
}, formatVersion(updateState.currentVersion))
|
||||
]),
|
||||
React.createElement('div', {
|
||||
key: 'new',
|
||||
className: 'flex justify-between items-center'
|
||||
}, [
|
||||
React.createElement('span', {
|
||||
key: 'new-label',
|
||||
className: 'text-sm text-gray-600 dark:text-gray-400'
|
||||
}, 'New version:'),
|
||||
React.createElement('span', {
|
||||
key: 'new-value',
|
||||
className: 'text-sm font-mono text-blue-600 dark:text-blue-400 font-semibold'
|
||||
}, formatVersion(updateState.newVersion))
|
||||
])
|
||||
]),
|
||||
|
||||
// Update progress
|
||||
updateState.isUpdating && React.createElement('div', {
|
||||
key: 'progress',
|
||||
className: 'mb-6'
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'progress-bar',
|
||||
className: 'w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2.5 mb-2'
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'progress-fill',
|
||||
className: 'bg-blue-500 h-2.5 rounded-full transition-all duration-300',
|
||||
style: {
|
||||
width: `${updateState.progress}%`
|
||||
}
|
||||
})
|
||||
]),
|
||||
React.createElement('p', {
|
||||
key: 'progress-text',
|
||||
className: 'text-center text-sm text-gray-600 dark:text-gray-400'
|
||||
}, `${updateState.progress}%`)
|
||||
]),
|
||||
|
||||
// Action buttons
|
||||
!updateState.isUpdating && React.createElement('div', {
|
||||
key: 'actions',
|
||||
className: 'flex gap-3'
|
||||
}, [
|
||||
React.createElement('button', {
|
||||
key: 'update-btn',
|
||||
onClick: handleForceUpdate,
|
||||
className: 'flex-1 bg-blue-500 hover:bg-blue-600 text-white font-semibold py-3 px-6 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2',
|
||||
disabled: updateState.isUpdating
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'update-icon',
|
||||
className: 'fas fa-download'
|
||||
}),
|
||||
React.createElement('span', {
|
||||
key: 'update-text'
|
||||
}, 'Update Now')
|
||||
]),
|
||||
React.createElement('button', {
|
||||
key: 'close-btn',
|
||||
onClick: handleCloseModal,
|
||||
className: 'px-4 py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors duration-200',
|
||||
disabled: updateState.isUpdating
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'close-icon',
|
||||
className: 'fas fa-times'
|
||||
})
|
||||
])
|
||||
]),
|
||||
|
||||
// Update indicator
|
||||
updateState.isUpdating && React.createElement('div', {
|
||||
key: 'updating',
|
||||
className: 'text-center'
|
||||
}, [
|
||||
React.createElement('p', {
|
||||
key: 'updating-text',
|
||||
className: 'text-sm text-gray-600 dark:text-gray-400'
|
||||
}, 'Update in progress...')
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
};
|
||||
|
||||
// Export for use
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = UpdateChecker;
|
||||
} else {
|
||||
window.UpdateChecker = UpdateChecker;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
const BecomePartner = () => {
|
||||
const partners = [
|
||||
{ id: 'aegis', name: 'Aegis', logo: 'logo/aegis.png', isColor: true, url: 'https://aegis-investment.com/' },
|
||||
{ id: 'furi', name: 'Furi Labs', logo: 'logo/furi.png', isColor: true, url: 'https://furilabs.com/' }
|
||||
];
|
||||
|
||||
const formUrl = 'https://docs.google.com/forms/d/e/1FAIpQLSc9ijV9PCoyXkus6vEx1OWwvwAsLq8fKS6-H5BmX-c-bvia6w/viewform?usp=dialog';
|
||||
|
||||
return React.createElement('div', { className: "mt-20 px-6" }, [
|
||||
// Header "Trusted by our partners"
|
||||
React.createElement('div', { key: 'header', className: "text-center max-w-3xl mx-auto mb-8" }, [
|
||||
React.createElement('h3', { key: 'title', className: "text-3xl font-bold text-primary mb-3" }, 'Trusted by our partners')
|
||||
]),
|
||||
|
||||
// First divider line with fade
|
||||
React.createElement('div', {
|
||||
key: 'divider-1',
|
||||
className: "h-px w-full max-w-3xl mx-auto mb-8 bg-gradient-to-r from-transparent via-zinc-700 to-transparent"
|
||||
}),
|
||||
|
||||
// Partner Logos
|
||||
React.createElement('div', {
|
||||
key: 'partners-row',
|
||||
className: "flex justify-center items-center flex-wrap gap-12 mb-8"
|
||||
},
|
||||
partners.map(partner =>
|
||||
React.createElement('a', {
|
||||
key: partner.id,
|
||||
href: partner.url,
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
className: "flex items-center justify-center cursor-pointer hover:opacity-100 transition-opacity duration-300"
|
||||
}, [
|
||||
React.createElement('img', {
|
||||
key: 'logo',
|
||||
src: partner.logo,
|
||||
alt: partner.name,
|
||||
className: "h-12 sm:h-16 opacity-80 hover:opacity-100 transition-opacity duration-300",
|
||||
style: partner.isColor ? {
|
||||
filter: 'grayscale(100%) brightness(1.2) contrast(1.1)',
|
||||
WebkitFilter: 'grayscale(100%) brightness(1.2) contrast(1.1)'
|
||||
} : {}
|
||||
})
|
||||
])
|
||||
)
|
||||
),
|
||||
|
||||
// Second divider line with fade
|
||||
React.createElement('div', {
|
||||
key: 'divider-2',
|
||||
className: "h-px w-full max-w-3xl mx-auto mb-8 bg-gradient-to-r from-transparent via-zinc-700 to-transparent"
|
||||
}),
|
||||
|
||||
// Section with subtitle and text
|
||||
React.createElement('div', { key: 'cta-section', className: "text-center max-w-3xl mx-auto" }, [
|
||||
React.createElement('h4', {
|
||||
key: 'subtitle',
|
||||
className: "text-base font-semibold text-primary mb-4"
|
||||
}, 'Technology & Community Partners'),
|
||||
|
||||
React.createElement('p', {
|
||||
key: 'description',
|
||||
className: "text-secondary text-sm mb-6"
|
||||
}, 'Interested in partnering with us?'),
|
||||
|
||||
// CTA Button with 3D glass effect
|
||||
React.createElement('div', {
|
||||
key: 'button-wrapper',
|
||||
className: "button-container flex justify-center"
|
||||
}, [
|
||||
React.createElement('a', {
|
||||
key: 'button-link',
|
||||
href: formUrl,
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
className: "button"
|
||||
}, [
|
||||
React.createElement('span', { key: 'text' }, 'Become a Partner')
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
};
|
||||
|
||||
window.BecomePartner = BecomePartner;
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
|
||||
|
||||
|
||||
const ComparisonTable = () => {
|
||||
const [selectedFeature, setSelectedFeature] = React.useState(null);
|
||||
|
||||
const messengers = [
|
||||
{
|
||||
name: "SecureBit.chat",
|
||||
logo: <div className="w-8 h-8 bg-orange-500/10 border border-orange-500/20 rounded-lg flex items-center justify-center">
|
||||
<i className="fas fa-shield-halved text-orange-400" />
|
||||
</div>,
|
||||
type: "P2P WebRTC",
|
||||
version: "Latest",
|
||||
color: "orange",
|
||||
},
|
||||
{
|
||||
name: "Signal",
|
||||
logo: (
|
||||
<svg className="w-8 h-8" viewBox="0 0 122.88 122.31" xmlns="http://www.w3.org/2000/svg">
|
||||
<path className="fill-blue-500" d="M27.75,0H95.13a27.83,27.83,0,0,1,27.75,27.75V94.57a27.83,27.83,0,0,1-27.75,27.74H27.75A27.83,27.83,0,0,1,0,94.57V27.75A27.83,27.83,0,0,1,27.75,0Z" />
|
||||
<path className="fill-white" d="M61.44,25.39A35.76,35.76,0,0,0,31.18,80.18L27.74,94.86l14.67-3.44a35.77,35.77,0,1,0,19-66Z" />
|
||||
</svg>
|
||||
),
|
||||
type: "Centralized",
|
||||
version: "Latest",
|
||||
color: "blue",
|
||||
},
|
||||
{
|
||||
name: "Threema",
|
||||
logo: (
|
||||
<svg className="w-8 h-8" viewBox="0 0 122.88 122.88" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="122.88" height="122.88" rx="18.43" fill="#474747" />
|
||||
<path fill="#FFFFFF" d="M44.26,78.48l-19.44,4.8l4.08-16.56c-4.08-5.28-6.48-12-6.48-18.96c0-18.96,17.52-34.32,39.12-34.32c21.6,0,39.12,15.36,39.12,34.32c0,18.96-17.52,34.32-39.12,34.32c-6,0-12-1.2-17.04-3.36L44.26,78.48z M50.26,44.64h-0.48c-0.96,0-1.68,0.72-1.44,1.68v15.6c0,0.96,0.72,1.68,1.68,1.68l23.04,0c0.96,0,1.68-0.72,1.68-1.68v-15.6c0-0.96-0.72-1.68-1.68-1.68h-0.48v-4.32c0-6-5.04-11.04-11.04-11.04S50.5,34.32,50.5,40.32v4.32H50.26z M68.02,44.64h-13.2v-4.32c0-3.6,2.88-6.72,6.72-6.72c3.6,0,6.72,2.88,6.72,6.72v4.32H68.02z" />
|
||||
<circle cx="37.44" cy="97.44" r="6.72" fill="#3fe669" />
|
||||
<circle cx="61.44" cy="97.44" r="6.72" fill="#3fe669" />
|
||||
<circle cx="85.44" cy="97.44" r="6.72" fill="#3fe669" />
|
||||
</svg>
|
||||
),
|
||||
type: "Centralized",
|
||||
version: "Latest",
|
||||
color: "green",
|
||||
},
|
||||
{
|
||||
name: "Session",
|
||||
logo: (
|
||||
<svg className="w-8 h-8" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1024" height="1024" fill="#333132" />
|
||||
<path fill="#00f782" d="M431 574.8c-.8-7.4-6.7-8.2-10.8-10.6-13.6-7.9-27.5-15.4-41.3-23l-22.5-12.3c-8.5-4.7-17.1-9.2-25.6-14.1-10.5-6-21-11.9-31.1-18.6-18.9-12.5-33.8-29.1-46.3-48.1-8.3-12.6-14.8-26.1-19.2-40.4-6.7-21.7-10.8-44.1-7.8-66.8 1.8-14 4.6-28 9.7-41.6 7.8-20.8 19.3-38.8 34.2-54.8 9.8-10.6 21.2-19.1 33.4-26.8 14.7-9.3 30.7-15.4 47.4-19 13.8-3 28.1-4.3 42.2-4.4 89.9-.4 179.7-.3 269.6 0 12.6 0 25.5 1 37.7 4.1 24.3 6.2 45.7 18.2 63 37 11.2 12.2 20.4 25.8 25.8 41.2 7.3 20.7 12.3 42.1 6.7 64.4-2.1 8.5-2.7 17.5-6.1 25.4-4.7 10.9-10.8 21.2-17.2 31.2-8.7 13.5-20.5 24.3-34.4 32.2-10.1 5.7-21 10.2-32 14.3-18.1 6.7-37.2 5-56.1 5.2-17.2.2-34.5 0-51.7.1-1.7 0-3.4 1.2-5.1 1.9 1.3 1.8 2.1 4.3 3.9 5.3 13.5 7.8 27.2 15.4 40.8 22.9 11 6 22.3 11.7 33.2 17.9 15.2 8.5 30.2 17.4 45.3 26.1 19.3 11.1 34.8 26.4 47.8 44.3 9.7 13.3 17.2 27.9 23 43.5 6.1 16.6 9.2 33.8 10.4 51.3.6 9.1-.7 18.5-1.9 27.6-1.2 9.1-2.7 18.4-5.6 27.1-3.3 10.2-7.4 20.2-12.4 29.6-8.4 15.7-19.6 29.4-32.8 41.4-12.7 11.5-26.8 20.6-42.4 27.6-22.9 10.3-46.9 14.4-71.6 14.5-89.7.3-179.4.2-269.1-.1-12.6 0-25.5-1-37.7-3.9-24.5-5.7-45.8-18-63.3-36.4-11.6-12.3-20.2-26.5-26.6-41.9-2.7-6.4-4.1-13.5-5.4-20.4-1.5-8.1-2.8-16.3-3.1-24.5-.6-15.7 2.8-30.9 8.2-45.4 8.2-22 21.7-40.6 40.2-55.2 10-7.9 21.3-13.7 33.1-18.8 16.6-7.2 34-8.1 51.4-8.5 21.9-.5 43.9-.1 65.9-.1 1.9-.1 3.9-.3 6.2-.4zm96.3-342.4c0 .1 0 .1 0 0-48.3.1-96.6-.6-144.9.5-13.5.3-27.4 3.9-40.1 8.7-14.9 5.6-28.1 14.6-39.9 25.8-20.2 19-32.2 42.2-37.2 68.9-3.6 19-1.4 38.1 4.1 56.5 4.1 13.7 10.5 26.4 18.5 38.4 14.8 22.2 35.7 36.7 58.4 49.2 11 6.1 22.2 11.9 33.2 18 13.5 7.5 26.9 15.1 40.4 22.6 13.1 7.3 26.2 14.5 39.2 21.7 9.7 5.3 19.4 10.7 29.1 16.1 2.9 1.6 4.1.2 4.5-2.4.3-2 .3-4 .3-6.1v-58.8c0-19.9.1-39.9 0-59.8 0-6.6 1.7-12.8 7.6-16.1 3.5-2 8.2-2.8 12.4-2.8 50.3-.2 100.7-.2 151-.1 19.8 0 38.3-4.4 55.1-15.1 23.1-14.8 36.3-36.3 40.6-62.9 3.4-20.8-1-40.9-12.4-58.5-17.8-27.5-43.6-43-76.5-43.6-47.8-.8-95.6-.2-143.4-.2zm-30.6 559.7c45.1 0 90.2-.2 135.3.1 18.9.1 36.6-3.9 53.9-11.1 18.4-7.7 33.6-19.8 46.3-34.9 9.1-10.8 16.2-22.9 20.8-36.5 4.2-12.4 7.4-24.7 7.3-37.9-.1-10.3.2-20.5-3.4-30.5-2.6-7.2-3.4-15.2-6.4-22.1-3.9-8.9-8.9-17.3-14-25.5-12.9-20.8-31.9-34.7-52.8-46.4-10.6-5.9-21.2-11.6-31.8-17.5-10.3-5.7-20.4-11.7-30.7-17.4-11.2-6.1-22.5-11.9-33.7-18-16.6-9.1-33.1-18.4-49.8-27.5-4.9-2.7-6.1-1.9-6.4 3.9-.1 2-.1 4.1-.1 6.1v114.5c0 14.8-5.6 20.4-20.4 20.4-47.6.1-95.3-.1-142.9.2-10.5.1-21.1 1.4-31.6 2.8-16.5 2.2-30.5 9.9-42.8 21-17 15.5-27 34.7-29.4 57.5-1.1 10.9-.4 21.7 2.9 32.5 3.7 12.3 9.2 23.4 17.5 33 19.2 22.1 43.4 33.3 72.7 33.3 46.6.1 93 0 139.5 0z" />
|
||||
</svg>
|
||||
),
|
||||
type: "Onion Network",
|
||||
version: "Latest",
|
||||
color: "cyan",
|
||||
},
|
||||
];
|
||||
|
||||
const features = [
|
||||
{
|
||||
name: "Security Architecture",
|
||||
lockbit: { status: "trophy", detail: "18-layer military-grade defense system with complete ASN.1 validation" },
|
||||
signal: { status: "check", detail: "Signal Protocol with double ratchet" },
|
||||
threema: { status: "check", detail: "Standard security implementation" },
|
||||
session: { status: "check", detail: "Modified Signal Protocol + Onion routing" },
|
||||
},
|
||||
{
|
||||
name: "Cryptography",
|
||||
lockbit: { status: "trophy", detail: "ECDH P-384 + AES-GCM 256 + ECDSA P-384" },
|
||||
signal: { status: "check", detail: "Signal Protocol + Double Ratchet" },
|
||||
threema: { status: "check", detail: "NaCl + XSalsa20 + Poly1305" },
|
||||
session: { status: "check", detail: "Modified Signal Protocol" },
|
||||
},
|
||||
{
|
||||
name: "Perfect Forward Secrecy",
|
||||
lockbit: { status: "trophy", detail: "Auto rotation every 5 minutes or 100 messages" },
|
||||
signal: { status: "check", detail: "Double Ratchet algorithm" },
|
||||
threema: { status: "warning", detail: "Partial (group chats)" },
|
||||
session: { status: "check", detail: "Session Ratchet algorithm" },
|
||||
},
|
||||
{
|
||||
name: "Architecture",
|
||||
lockbit: { status: "trophy", detail: "Pure P2P WebRTC without servers" },
|
||||
signal: { status: "times", detail: "Centralized Signal servers" },
|
||||
threema: { status: "times", detail: "Threema servers in Switzerland" },
|
||||
session: { status: "warning", detail: "Onion routing via network nodes" },
|
||||
},
|
||||
{
|
||||
name: "Registration Anonymity",
|
||||
lockbit: { status: "trophy", detail: "No registration required, instant anonymous channels" },
|
||||
signal: { status: "times", detail: "Phone number required" },
|
||||
threema: { status: "check", detail: "ID generated locally" },
|
||||
session: { status: "check", detail: "Random session ID" },
|
||||
},
|
||||
{
|
||||
name: "Metadata Protection",
|
||||
lockbit: { status: "trophy", detail: "Full metadata encryption + traffic obfuscation" },
|
||||
signal: { status: "warning", detail: "Sealed Sender (partial)" },
|
||||
threema: { status: "warning", detail: "Minimal metadata" },
|
||||
session: { status: "check", detail: "Onion routing hides metadata" },
|
||||
},
|
||||
{
|
||||
name: "Traffic Obfuscation",
|
||||
lockbit: { status: "trophy", detail: "Fake traffic + pattern masking + packet padding" },
|
||||
signal: { status: "times", detail: "No traffic obfuscation" },
|
||||
threema: { status: "times", detail: "No traffic obfuscation" },
|
||||
session: { status: "check", detail: "Onion routing provides obfuscation" },
|
||||
},
|
||||
{
|
||||
name: "Open Source",
|
||||
lockbit: { status: "trophy", detail: "100% open + auditable + MIT license" },
|
||||
signal: { status: "check", detail: "Fully open" },
|
||||
threema: { status: "warning", detail: "Only clients open" },
|
||||
session: { status: "check", detail: "Fully open" },
|
||||
},
|
||||
{
|
||||
name: "MITM Protection",
|
||||
lockbit: { status: "trophy", detail: "Out-of-band verification + mutual auth + ECDSA" },
|
||||
signal: { status: "check", detail: "Safety numbers verification" },
|
||||
threema: { status: "check", detail: "QR code scanning" },
|
||||
session: { status: "warning", detail: "Basic key verification" },
|
||||
},
|
||||
{
|
||||
name: "Censorship Resistance",
|
||||
lockbit: { status: "trophy", detail: "Impossible to block P2P + no servers to target" },
|
||||
signal: { status: "warning", detail: "Blocked in authoritarian countries" },
|
||||
threema: { status: "warning", detail: "May be blocked" },
|
||||
session: { status: "check", detail: "Onion routing bypasses blocks" },
|
||||
},
|
||||
{
|
||||
name: "Data Storage",
|
||||
lockbit: { status: "trophy", detail: "Zero data storage - only in browser memory" },
|
||||
signal: { status: "warning", detail: "Local database storage" },
|
||||
threema: { status: "warning", detail: "Local + optional backup" },
|
||||
session: { status: "warning", detail: "Local database storage" },
|
||||
},
|
||||
{
|
||||
name: "Key Security",
|
||||
lockbit: { status: "trophy", detail: "Non-extractable keys + hardware protection" },
|
||||
signal: { status: "check", detail: "Secure key storage" },
|
||||
threema: { status: "check", detail: "Local key storage" },
|
||||
session: { status: "check", detail: "Secure key storage" },
|
||||
},
|
||||
{
|
||||
name: "Post-Quantum Roadmap",
|
||||
lockbit: { status: "check", detail: "Planned v5.0 - CRYSTALS-Kyber/Dilithium" },
|
||||
signal: { status: "warning", detail: "PQXDH in development" },
|
||||
threema: { status: "times", detail: "Not announced" },
|
||||
session: { status: "times", detail: "Not announced" },
|
||||
},
|
||||
];
|
||||
|
||||
const getStatusIcon = (status) => {
|
||||
const statusMap = {
|
||||
"trophy": { icon: "fa-trophy", color: "accent-orange" },
|
||||
"check": { icon: "fa-check", color: "text-green-300" },
|
||||
"warning": { icon: "fa-exclamation-triangle", color: "text-yellow-300" },
|
||||
"times": { icon: "fa-times", color: "text-red-300" },
|
||||
};
|
||||
return statusMap[status] || { icon: "fa-question", color: "text-gray-400" };
|
||||
};
|
||||
|
||||
const toggleFeatureDetail = (index) => {
|
||||
setSelectedFeature(selectedFeature === index ? null : index);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-16">
|
||||
{/* Title */}
|
||||
<div className="text-center mb-8">
|
||||
<h3 className="text-3xl font-bold text-white mb-3">
|
||||
Enhanced Security Edition Comparison
|
||||
</h3>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto mb-4">
|
||||
Enhanced Security Edition vs leading secure messengers
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Table container */}
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Mobile Alert */}
|
||||
<div className="md:hidden p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg mb-4">
|
||||
<p className="text-yellow-400 text-sm text-center">
|
||||
<i className="fas fa-lightbulb mr-2"></i>
|
||||
Rotate your device horizontally for better viewing
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="overflow-x-auto">
|
||||
<table
|
||||
className="w-full border-collapse rounded-xl overflow-hidden shadow-2xl"
|
||||
style={{ backgroundColor: "rgba(42, 43, 42, 0.9)" }}
|
||||
>
|
||||
{/* Table Header */}
|
||||
<thead>
|
||||
<tr className="bg-black-table">
|
||||
<th className="text-left p-4 border-b border-gray-600 text-white font-bold min-w-[240px]">
|
||||
Security Criterion
|
||||
</th>
|
||||
{messengers.map((messenger, index) => (
|
||||
<th key={`messenger-${index}`} className="text-center p-4 border-b border-gray-600 min-w-[160px]">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="mb-2">{messenger.logo}</div>
|
||||
<div className={`text-sm font-bold ${
|
||||
messenger.color === 'orange' ? 'text-orange-400' :
|
||||
messenger.color === 'blue' ? 'text-blue-400' :
|
||||
messenger.color === 'green' ? 'text-green-400' :
|
||||
'text-cyan-400'
|
||||
}`}>
|
||||
{messenger.name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">{messenger.type}</div>
|
||||
<div className="text-xs text-gray-500 mt-1">{messenger.version}</div>
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{/* Table body */}
|
||||
<tbody>
|
||||
{features.map((feature, featureIndex) => (
|
||||
<React.Fragment key={`feature-${featureIndex}`}>
|
||||
<tr
|
||||
className={`border-b border-gray-700/30 transition-all duration-200 cursor-pointer hover:bg-[rgb(20_20_20_/30%)] ${
|
||||
selectedFeature === featureIndex ? 'bg-[rgb(20_20_20_/50%)]' : ''
|
||||
}`}
|
||||
onClick={() => toggleFeatureDetail(featureIndex)}
|
||||
>
|
||||
<td className="p-4 text-white font-semibold">
|
||||
<div className="flex items-center justify-between">
|
||||
<span>{feature.name}</span>
|
||||
<i className={`fas fa-chevron-${selectedFeature === featureIndex ? 'up' : 'down'} text-xs text-gray-400 opacity-60 transition-all duration-200`} />
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4 text-center">
|
||||
<i className={`fas ${getStatusIcon(feature.lockbit.status).icon} ${getStatusIcon(feature.lockbit.status).color} text-2xl`} />
|
||||
</td>
|
||||
<td className="p-4 text-center">
|
||||
<i className={`fas ${getStatusIcon(feature.signal.status).icon} ${getStatusIcon(feature.signal.status).color} text-2xl`} />
|
||||
</td>
|
||||
<td className="p-4 text-center">
|
||||
<i className={`fas ${getStatusIcon(feature.threema.status).icon} ${getStatusIcon(feature.threema.status).color} text-2xl`} />
|
||||
</td>
|
||||
<td className="p-4 text-center">
|
||||
<i className={`fas ${getStatusIcon(feature.session.status).icon} ${getStatusIcon(feature.session.status).color} text-2xl`} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Details */}
|
||||
{selectedFeature === featureIndex && (
|
||||
<tr className="border-b border-gray-700/30 bg-gradient-to-r from-gray-800/20 to-gray-900/20">
|
||||
<td className="p-4 text-xs text-gray-400 font-medium">Technical Details:</td>
|
||||
<td className="p-4 text-center">
|
||||
<div className="text-xs text-orange-300 font-medium leading-relaxed">
|
||||
{feature.lockbit.detail}
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4 text-center">
|
||||
<div className="text-xs text-blue-300 leading-relaxed">
|
||||
{feature.signal.detail}
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4 text-center">
|
||||
<div className="text-xs text-green-300 leading-relaxed">
|
||||
{feature.threema.detail}
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4 text-center">
|
||||
<div className="text-xs text-cyan-300 leading-relaxed">
|
||||
{feature.session.detail}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Legend */}
|
||||
<div className="mt-8 grid grid-cols-2 md:grid-cols-4 gap-4 max-w-5xl mx-auto">
|
||||
<div className="flex items-center justify-center p-4 bg-orange-500/10 rounded-xl hover:bg-orange-500/40 transition-colors">
|
||||
<i className="fas fa-trophy text-orange-400 mr-2 text-xl"></i>
|
||||
<span className="text-orange-300 text-sm font-bold">Category Leader</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center p-4 bg-green-500/10 rounded-xl hover:bg-green-600/40 transition-colors">
|
||||
<i className="fas fa-check text-green-300 mr-2 text-xl"></i>
|
||||
<span className="text-green-200 text-sm font-bold">Excellent</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center p-4 bg-yellow-500/10 rounded-xl hover:bg-yellow-600/40 transition-colors">
|
||||
<i className="fas fa-exclamation-triangle text-yellow-300 mr-2 text-xl"></i>
|
||||
<span className="text-yellow-200 text-sm font-bold">Partial/Limited</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center p-4 bg-red-500/10 rounded-xl hover:bg-red-600/40 transition-colors">
|
||||
<i className="fas fa-times text-red-300 mr-2 text-xl"></i>
|
||||
<span className="text-red-200 text-sm font-bold">Not Available</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
window.ComparisonTable = ComparisonTable;
|
||||
@@ -1,21 +1,63 @@
|
||||
const DownloadApps = () => {
|
||||
const apps = [
|
||||
{ id: 'web', name: 'Web App', subtitle: 'Browser Version', icon: 'fas fa-globe', platform: 'Web', isActive: true, url: 'https://securebitchat.github.io/securebit-chat/', color: 'green' },
|
||||
{ id: 'windows', name: 'Windows', subtitle: 'Desktop App', icon: 'fab fa-windows', platform: 'Desktop', isActive: false, url: '#', color: 'blue' },
|
||||
{ id: 'macos', name: 'macOS', subtitle: 'Desktop App', icon: 'fab fa-apple', platform: 'Desktop', isActive: false, url: '#', color: 'gray' },
|
||||
{ id: 'linux', name: 'Linux', subtitle: 'Desktop App', icon: 'fab fa-linux', platform: 'Desktop', isActive: false, url: '#', color: 'orange' },
|
||||
{ id: 'ios', name: 'iOS', subtitle: 'iPhone & iPad', icon: 'fab fa-apple', platform: 'Mobile', isActive: false, url: 'https://apps.apple.com/app/securebit-chat/', color: 'blue' },
|
||||
{ id: 'android', name: 'Android', subtitle: 'Google Play', icon: 'fab fa-android', platform: 'Mobile', isActive: false, url: 'https://play.google.com/store/apps/details?id=com.securebit.chat', color: 'green' }
|
||||
{ id: 'web', name: 'Web App', subtitle: 'Browser Version', icon: 'fas fa-globe', platform: 'Web', isActive: true, url: 'https://securebit.chat/', color: 'green' },
|
||||
{ id: 'windows', name: 'Windows', subtitle: 'Desktop App', icon: 'fab fa-windows', platform: 'Desktop', isActive: true, url: 'https://github.com/SecureBitChat/securebit-desktop/releases/latest/download/SecureBit.Chat_0.1.0_x64-setup.exe', color: 'blue' },
|
||||
{ id: 'macos', name: 'macOS', subtitle: 'Desktop App', icon: 'fab fa-safari', platform: 'Desktop', isActive: true, url: 'https://github.com/SecureBitChat/securebit-desktop/releases/download/v0.1.0/SecureBit.Chat_0.1.0_x64.dmg', color: 'gray' },
|
||||
{ id: 'linux', name: 'Linux', subtitle: 'Desktop App', icon: 'fab fa-linux', platform: 'Desktop', isActive: true, url: 'https://github.com/SecureBitChat/securebit-desktop/releases/latest/download/SecureBit.Chat_0.1.0_amd64.AppImage', color: 'orange' },
|
||||
{ id: 'ios', name: 'iOS', subtitle: 'iPhone & iPad', icon: 'fab fa-apple', platform: 'Mobile', isActive: false, url: 'https://apps.apple.com/app/securebit-chat/', color: 'white' },
|
||||
{ id: 'android', name: 'Android', subtitle: 'Google Play', icon: 'fab fa-android', platform: 'Mobile', isActive: false, url: 'https://play.google.com/store/apps/details?id=com.securebit.chat', color: 'green' },
|
||||
{ id: 'chrome', name: 'Chrome', subtitle: 'Browser Extension', icon: 'fab fa-chrome', platform: 'Browser', isActive: false, url: '#', color: 'yellow' },
|
||||
{ id: 'edge', name: 'Edge', subtitle: 'Browser Extension', icon: 'fab fa-edge', platform: 'Browser', isActive: false, url: '#', color: 'blue' },
|
||||
{ id: 'opera', name: 'Opera', subtitle: 'Browser Extension', icon: 'fab fa-opera', platform: 'Browser', isActive: false, url: '#', color: 'red' },
|
||||
{ id: 'firefox', name: 'Firefox', subtitle: 'Browser Extension', icon: 'fab fa-firefox-browser', platform: 'Browser', isActive: false, url: '#', color: 'orange' },
|
||||
];
|
||||
|
||||
const handleDownload = (app) => {
|
||||
if (app.isActive) window.open(app.url, '_blank');
|
||||
};
|
||||
|
||||
const desktopApps = apps.filter(a => a.platform !== 'Mobile');
|
||||
const desktopApps = apps.filter(a => a.platform === 'Desktop' || a.platform === 'Web');
|
||||
const mobileApps = apps.filter(a => a.platform === 'Mobile');
|
||||
const browserApps = apps.filter(a => a.platform === 'Browser');
|
||||
|
||||
const cardSize = "w-28 h-28";
|
||||
const cardSize = "w-28 h-28";
|
||||
|
||||
const colorClasses = {
|
||||
green: 'text-green-500',
|
||||
blue: 'text-blue-500',
|
||||
gray: 'text-gray-500',
|
||||
orange: 'text-orange-500',
|
||||
red: 'text-red-500',
|
||||
white: 'text-white',
|
||||
yellow: 'text-yellow-400',
|
||||
};
|
||||
|
||||
const renderAppCard = (app) => (
|
||||
React.createElement('div', {
|
||||
key: app.id,
|
||||
className: `group relative ${cardSize} rounded-2xl overflow-hidden card-minimal cursor-pointer`
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'bg-icon',
|
||||
className: `${app.icon} absolute text-[3rem] ${app.isActive ? colorClasses[app.color] : 'text-white/10'} top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none transition-all duration-500 group-hover:scale-105`
|
||||
}),
|
||||
React.createElement('div', {
|
||||
key: 'overlay',
|
||||
className: "absolute inset-0 bg-black/30 backdrop-blur-md flex flex-col items-center justify-center text-center opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||
}, [
|
||||
React.createElement('h4', { key: 'name', className: `text-sm font-semibold text-primary mb-1` }, app.name),
|
||||
React.createElement('p', { key: 'subtitle', className: `text-xs text-secondary mb-2` }, app.subtitle),
|
||||
app.isActive ?
|
||||
React.createElement('button', {
|
||||
key: 'btn',
|
||||
onClick: () => handleDownload(app),
|
||||
className: `px-2 py-1 rounded-xl bg-emerald-500 text-black font-medium hover:bg-emerald-600 transition-colors text-xs`
|
||||
}, app.id === "web" ? "Launch" : "Download")
|
||||
:
|
||||
React.createElement('span', { key: 'coming', className: "text-gray-400 font-medium text-xs" }, "Coming Soon")
|
||||
])
|
||||
])
|
||||
);
|
||||
|
||||
return React.createElement('div', { className: "mt-20 px-6" }, [
|
||||
// Header
|
||||
@@ -24,62 +66,19 @@ const DownloadApps = () => {
|
||||
React.createElement('p', { key: 'subtitle', className: "text-secondary text-lg mb-5" }, 'Stay secure on every device. Choose your platform and start chatting privately.')
|
||||
]),
|
||||
|
||||
// Desktop Apps
|
||||
React.createElement('div', { key: 'desktop-row', className: "hidden sm:flex justify-center flex-wrap gap-6 mb-6" },
|
||||
desktopApps.map(app =>
|
||||
React.createElement('div', {
|
||||
key: app.id,
|
||||
className: `group relative ${cardSize} rounded-2xl overflow-hidden card-minimal cursor-pointer`
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'bg-icon',
|
||||
className: `${app.icon} absolute text-[3rem] text-white/10 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none transition-all duration-500 group-hover:scale-105`
|
||||
}),
|
||||
React.createElement('div', {
|
||||
key: 'overlay',
|
||||
className: "absolute inset-0 bg-black/30 backdrop-blur-md flex flex-col items-center justify-center text-center opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||
}, [
|
||||
React.createElement('h4', { key: 'name', className: `text-sm font-semibold text-primary mb-1` }, app.name),
|
||||
React.createElement('p', { key: 'subtitle', className: `text-xs text-secondary mb-2` }, app.subtitle),
|
||||
app.isActive ?
|
||||
React.createElement('button', {
|
||||
key: 'btn',
|
||||
onClick: () => handleDownload(app),
|
||||
className: `px-2 py-1 rounded-xl bg-emerald-500 text-black font-medium hover:bg-emerald-600 transition-colors text-xs`
|
||||
}, app.id === "web" ? "Launch" : "Download")
|
||||
:
|
||||
React.createElement('span', { key: 'coming', className: "text-gray-400 font-medium text-xs" }, "Coming Soon")
|
||||
])
|
||||
])
|
||||
)
|
||||
desktopApps.map(renderAppCard)
|
||||
),
|
||||
|
||||
React.createElement('div', { key: 'mobile-row', className: "flex justify-center gap-6" },
|
||||
mobileApps.map(app =>
|
||||
React.createElement('div', {
|
||||
key: app.id,
|
||||
className: `group relative ${cardSize} rounded-2xl overflow-hidden card-minimal cursor-pointer`
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'bg-icon',
|
||||
className: `${app.icon} absolute text-[3rem] text-white/10 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none transition-all duration-500 group-hover:scale-105`
|
||||
}),
|
||||
React.createElement('div', {
|
||||
key: 'overlay',
|
||||
className: "absolute inset-0 bg-black/30 backdrop-blur-md flex flex-col items-center justify-center text-center opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||
}, [
|
||||
React.createElement('h4', { key: 'name', className: `text-sm font-semibold text-primary mb-1` }, app.name),
|
||||
React.createElement('p', { key: 'subtitle', className: `text-xs text-secondary mb-2` }, app.subtitle),
|
||||
app.isActive ?
|
||||
React.createElement('button', {
|
||||
key: 'btn',
|
||||
onClick: () => handleDownload(app),
|
||||
className: `px-2 py-1 rounded-xl bg-emerald-500 text-black font-medium hover:bg-emerald-600 transition-colors text-xs`
|
||||
}, "Download")
|
||||
:
|
||||
React.createElement('span', { key: 'coming', className: "text-gray-400 font-medium text-xs" }, "Coming Soon")
|
||||
])
|
||||
])
|
||||
)
|
||||
// Mobile Apps
|
||||
React.createElement('div', { key: 'mobile-row', className: "flex justify-center gap-6 mb-6" },
|
||||
mobileApps.map(renderAppCard)
|
||||
),
|
||||
|
||||
// Browser Extensions
|
||||
React.createElement('div', { key: 'browser-row', className: "flex justify-center gap-6" },
|
||||
browserApps.map(renderAppCard)
|
||||
)
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
const FileTransferComponent = ({ webrtcManager, isConnected }) => {
|
||||
const [dragOver, setDragOver] = React.useState(false);
|
||||
const [transfers, setTransfers] = React.useState({ sending: [], receiving: [] });
|
||||
const [readyFiles, setReadyFiles] = React.useState([]); // файлы, готовые к скачиванию
|
||||
const [pendingIncomingFiles, setPendingIncomingFiles] = React.useState([]);
|
||||
const fileInputRef = React.useRef(null);
|
||||
|
||||
// Update transfers periodically
|
||||
@@ -17,6 +19,14 @@ const FileTransferComponent = ({ webrtcManager, isConnected }) => {
|
||||
return () => clearInterval(interval);
|
||||
}, [isConnected, webrtcManager]);
|
||||
|
||||
// Clear session-local UI state when the connection ends so reconnect starts clean.
|
||||
React.useEffect(() => {
|
||||
if (isConnected) return;
|
||||
setReadyFiles([]);
|
||||
setPendingIncomingFiles([]);
|
||||
setTransfers({ sending: [], receiving: [] });
|
||||
}, [isConnected]);
|
||||
|
||||
// Setup file transfer callbacks - ИСПРАВЛЕНИЕ: НЕ отправляем промежуточные сообщения в чат
|
||||
React.useEffect(() => {
|
||||
if (!webrtcManager) return;
|
||||
@@ -24,8 +34,6 @@ const FileTransferComponent = ({ webrtcManager, isConnected }) => {
|
||||
webrtcManager.setFileTransferCallbacks(
|
||||
// Progress callback - ТОЛЬКО обновляем UI, НЕ отправляем в чат
|
||||
(progress) => {
|
||||
console.log(`📁 UI Progress: ${progress.fileName}: ${progress.progress.toFixed(1)}% (${progress.status})`);
|
||||
|
||||
// Обновляем только локальное состояние
|
||||
const currentTransfers = webrtcManager.getFileTransfers();
|
||||
setTransfers(currentTransfers);
|
||||
@@ -33,36 +41,49 @@ const FileTransferComponent = ({ webrtcManager, isConnected }) => {
|
||||
// НЕ отправляем сообщения в чат!
|
||||
},
|
||||
|
||||
// File received callback - показываем только финальное уведомление
|
||||
// File received callback - добавляем кнопку скачивания в UI
|
||||
(fileData) => {
|
||||
console.log(`📥 File received in UI: ${fileData.fileName}`);
|
||||
|
||||
// Auto-download received file
|
||||
const url = URL.createObjectURL(fileData.fileBlob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = fileData.fileName;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
// Update transfer list
|
||||
// Добавляем в список готовых к скачиванию
|
||||
setReadyFiles(prev => {
|
||||
// избегаем дублей по fileId
|
||||
if (prev.some(f => f.fileId === fileData.fileId)) return prev;
|
||||
return [...prev, {
|
||||
fileId: fileData.fileId,
|
||||
fileName: fileData.fileName,
|
||||
fileSize: fileData.fileSize,
|
||||
mimeType: fileData.mimeType,
|
||||
getBlob: fileData.getBlob,
|
||||
getObjectURL: fileData.getObjectURL,
|
||||
revokeObjectURL: fileData.revokeObjectURL
|
||||
}];
|
||||
});
|
||||
|
||||
// Обновляем список активных передач
|
||||
const currentTransfers = webrtcManager.getFileTransfers();
|
||||
setTransfers(currentTransfers);
|
||||
|
||||
// ИСПРАВЛЕНИЕ: НЕ дублируем системные сообщения
|
||||
// Финальное уведомление уже отправляется в WebRTC менеджере
|
||||
},
|
||||
|
||||
// Error callback
|
||||
(error) => {
|
||||
console.error('File transfer error in UI:', error);
|
||||
const currentTransfers = webrtcManager.getFileTransfers();
|
||||
setTransfers(currentTransfers);
|
||||
|
||||
// ИСПРАВЛЕНИЕ: НЕ дублируем сообщения об ошибках
|
||||
// Уведомления об ошибках уже отправляются в WebRTC менеджере
|
||||
},
|
||||
|
||||
// Incoming file request callback - user consent is mandatory
|
||||
(fileRequest) => {
|
||||
setPendingIncomingFiles(prev => {
|
||||
if (prev.some(file => file.fileId === fileRequest.fileId)) return prev;
|
||||
return [...prev, fileRequest];
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
webrtcManager.setFileTransferCallbacks(null, null, null, null);
|
||||
};
|
||||
}, [webrtcManager]);
|
||||
|
||||
const handleFileSelect = async (files) => {
|
||||
@@ -79,19 +100,27 @@ const FileTransferComponent = ({ webrtcManager, isConnected }) => {
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
console.log(`🚀 Starting file upload from UI: ${file.name}`);
|
||||
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Валидация файла перед отправкой
|
||||
const validation = webrtcManager.validateFile(file);
|
||||
if (!validation.isValid) {
|
||||
const errorMessage = validation.errors.join('. ');
|
||||
alert(`Файл ${file.name} не может быть отправлен: ${errorMessage}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await webrtcManager.sendFile(file);
|
||||
} catch (error) {
|
||||
// Более мягкая обработка ошибок - не закрываем сессию
|
||||
console.error(`Failed to send ${file.name}:`, error);
|
||||
|
||||
// Показываем пользователю ошибку, но не закрываем соединение
|
||||
if (error.message.includes('Connection not ready')) {
|
||||
alert(`Файл ${file.name} не может быть отправлен сейчас. Проверьте соединение и попробуйте снова.`);
|
||||
} else if (error.message.includes('File too large')) {
|
||||
alert(`Файл ${file.name} слишком большой. Максимальный размер: 100 MB`);
|
||||
} else if (error.message.includes('File too large') || error.message.includes('exceeds maximum')) {
|
||||
alert(`Файл ${file.name} слишком большой: ${error.message}`);
|
||||
} else if (error.message.includes('Maximum concurrent transfers')) {
|
||||
alert(`Достигнут лимит одновременных передач. Дождитесь завершения текущих передач.`);
|
||||
} else if (error.message.includes('File type not allowed')) {
|
||||
alert(`Тип файла ${file.name} не поддерживается: ${error.message}`);
|
||||
} else {
|
||||
alert(`Ошибка отправки файла ${file.name}: ${error.message}`);
|
||||
}
|
||||
@@ -169,6 +198,19 @@ const FileTransferComponent = ({ webrtcManager, isConnected }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleIncomingDecision = async (fileId, accepted) => {
|
||||
try {
|
||||
if (accepted) {
|
||||
await webrtcManager.acceptIncomingFile(fileId);
|
||||
} else {
|
||||
await webrtcManager.rejectIncomingFile(fileId);
|
||||
}
|
||||
} finally {
|
||||
setPendingIncomingFiles(prev => prev.filter(file => file.fileId !== fileId));
|
||||
setTransfers(webrtcManager.getFileTransfers());
|
||||
}
|
||||
};
|
||||
|
||||
if (!isConnected) {
|
||||
return React.createElement('div', {
|
||||
className: "p-4 text-center text-muted"
|
||||
@@ -213,11 +255,11 @@ const FileTransferComponent = ({ webrtcManager, isConnected }) => {
|
||||
React.createElement('p', {
|
||||
key: 'text',
|
||||
className: "text-primary font-medium"
|
||||
}, 'Перетащите файлы сюда или нажмите для выбора'),
|
||||
}, 'Drag files here or click to select'),
|
||||
React.createElement('p', {
|
||||
key: 'subtext',
|
||||
className: "text-muted text-sm"
|
||||
}, 'Максимальный размер: 100 МБ на файл')
|
||||
}, 'Maximum size: 100 MB per file')
|
||||
])
|
||||
]),
|
||||
|
||||
@@ -231,6 +273,45 @@ const FileTransferComponent = ({ webrtcManager, isConnected }) => {
|
||||
onChange: handleFileInputChange
|
||||
}),
|
||||
|
||||
pendingIncomingFiles.length > 0 && React.createElement('div', {
|
||||
key: 'incoming-consent',
|
||||
className: "mt-4 space-y-2"
|
||||
}, pendingIncomingFiles.map(file => React.createElement('div', {
|
||||
key: file.fileId,
|
||||
className: "rounded-lg border border-yellow-500/30 bg-yellow-500/10 p-3"
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'info',
|
||||
className: "mb-3 flex items-center justify-between gap-3"
|
||||
}, [
|
||||
React.createElement('div', { key: 'text' }, [
|
||||
React.createElement('div', {
|
||||
key: 'title',
|
||||
className: "text-sm font-medium text-primary"
|
||||
}, 'Incoming file request'),
|
||||
React.createElement('div', {
|
||||
key: 'meta',
|
||||
className: "text-xs text-secondary"
|
||||
}, `${file.fileName} · ${formatFileSize(file.fileSize)} · ${file.mimeType}`)
|
||||
])
|
||||
]),
|
||||
React.createElement('div', {
|
||||
key: 'actions',
|
||||
className: "flex gap-2"
|
||||
}, [
|
||||
React.createElement('button', {
|
||||
key: 'accept',
|
||||
onClick: () => handleIncomingDecision(file.fileId, true),
|
||||
className: "rounded-md bg-green-500/20 px-3 py-2 text-sm text-green-300 hover:bg-green-500/30"
|
||||
}, 'Accept'),
|
||||
React.createElement('button', {
|
||||
key: 'reject',
|
||||
onClick: () => handleIncomingDecision(file.fileId, false),
|
||||
className: "rounded-md bg-red-500/20 px-3 py-2 text-sm text-red-300 hover:bg-red-500/30"
|
||||
}, 'Reject')
|
||||
])
|
||||
]))),
|
||||
|
||||
// Active Transfers
|
||||
(transfers.sending.length > 0 || transfers.receiving.length > 0) && React.createElement('div', {
|
||||
key: 'transfers',
|
||||
@@ -342,14 +423,39 @@ const FileTransferComponent = ({ webrtcManager, isConnected }) => {
|
||||
className: "text-muted text-xs ml-2"
|
||||
}, formatFileSize(transfer.fileSize))
|
||||
]),
|
||||
React.createElement('button', {
|
||||
key: 'cancel',
|
||||
onClick: () => webrtcManager.cancelFileTransfer(transfer.fileId),
|
||||
className: "text-red-400 hover:text-red-300 text-xs"
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
className: 'fas fa-times'
|
||||
})
|
||||
React.createElement('div', { key: 'actions', className: 'flex items-center space-x-2' }, [
|
||||
(() => {
|
||||
const rf = readyFiles.find(f => f.fileId === transfer.fileId);
|
||||
if (!rf || transfer.status !== 'completed') return null;
|
||||
return React.createElement('button', {
|
||||
key: 'download',
|
||||
className: 'text-green-400 hover:text-green-300 text-xs flex items-center',
|
||||
onClick: async () => {
|
||||
try {
|
||||
const url = await rf.getObjectURL();
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = rf.fileName || 'file';
|
||||
a.click();
|
||||
rf.revokeObjectURL(url);
|
||||
} catch (e) {
|
||||
alert(e.message || 'This file is no longer available for download.');
|
||||
}
|
||||
}
|
||||
}, [
|
||||
React.createElement('i', { key: 'i', className: 'fas fa-download mr-1' }),
|
||||
'Download'
|
||||
]);
|
||||
})(),
|
||||
React.createElement('button', {
|
||||
key: 'cancel',
|
||||
onClick: () => webrtcManager.cancelFileTransfer(transfer.fileId),
|
||||
className: "text-red-400 hover:text-red-300 text-xs"
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
className: 'fas fa-times'
|
||||
})
|
||||
])
|
||||
])
|
||||
]),
|
||||
React.createElement('div', {
|
||||
@@ -387,4 +493,4 @@ const FileTransferComponent = ({ webrtcManager, isConnected }) => {
|
||||
};
|
||||
|
||||
// Export
|
||||
window.FileTransferComponent = FileTransferComponent;
|
||||
window.FileTransferComponent = FileTransferComponent;
|
||||
|
||||
@@ -5,15 +5,14 @@ const EnhancedMinimalHeader = ({
|
||||
onDisconnect,
|
||||
isConnected,
|
||||
securityLevel,
|
||||
sessionManager,
|
||||
sessionTimeLeft,
|
||||
webrtcManager
|
||||
}) => {
|
||||
const [currentTimeLeft, setCurrentTimeLeft] = React.useState(sessionTimeLeft || 0);
|
||||
const [hasActiveSession, setHasActiveSession] = React.useState(false);
|
||||
const [sessionType, setSessionType] = React.useState('unknown');
|
||||
const [realSecurityLevel, setRealSecurityLevel] = React.useState(null);
|
||||
const [lastSecurityUpdate, setLastSecurityUpdate] = React.useState(0);
|
||||
// Added local session state to remove references errors after session timer removal
|
||||
const [hasActiveSession, setHasActiveSession] = React.useState(false);
|
||||
const [currentTimeLeft, setCurrentTimeLeft] = React.useState(0);
|
||||
const [sessionType, setSessionType] = React.useState('unknown');
|
||||
|
||||
// ============================================
|
||||
// FIXED SECURITY UPDATE LOGIC
|
||||
@@ -53,19 +52,6 @@ const EnhancedMinimalHeader = ({
|
||||
realSecurityData = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(activeWebrtcManager);
|
||||
}
|
||||
|
||||
if (window.DEBUG_MODE) {
|
||||
console.log('🔐 REAL security level calculated:', {
|
||||
level: realSecurityData?.level,
|
||||
score: realSecurityData?.score,
|
||||
passedChecks: realSecurityData?.passedChecks,
|
||||
totalChecks: realSecurityData?.totalChecks,
|
||||
isRealData: realSecurityData?.isRealData,
|
||||
sessionType: realSecurityData?.sessionType,
|
||||
maxPossibleScore: realSecurityData?.maxPossibleScore,
|
||||
verificationResults: realSecurityData?.verificationResults ? Object.keys(realSecurityData.verificationResults) : []
|
||||
});
|
||||
}
|
||||
|
||||
if (realSecurityData && realSecurityData.isRealData !== false) {
|
||||
const currentScore = realSecurityLevel?.score || 0;
|
||||
const newScore = realSecurityData.score || 0;
|
||||
@@ -73,23 +59,15 @@ const EnhancedMinimalHeader = ({
|
||||
if (currentScore !== newScore || !realSecurityLevel) {
|
||||
setRealSecurityLevel(realSecurityData);
|
||||
setLastSecurityUpdate(now);
|
||||
|
||||
if (window.DEBUG_MODE) {
|
||||
console.log('✅ Security level updated in header component:', {
|
||||
oldScore: currentScore,
|
||||
newScore: newScore,
|
||||
sessionType: realSecurityData.sessionType
|
||||
});
|
||||
}
|
||||
} else if (window.DEBUG_MODE) {
|
||||
console.log('ℹ️ Security level unchanged, skipping update');
|
||||
|
||||
} else if (window.DEBUG_MODE) {
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ Security calculation returned invalid data');
|
||||
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error in real security calculation:', error);
|
||||
|
||||
} finally {
|
||||
isUpdating = false;
|
||||
}
|
||||
@@ -114,7 +92,7 @@ const EnhancedMinimalHeader = ({
|
||||
const interval = setInterval(updateRealSecurityStatus, 30000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [webrtcManager, isConnected, lastSecurityUpdate, realSecurityLevel]);
|
||||
}, [webrtcManager, isConnected]);
|
||||
|
||||
// ============================================
|
||||
// FIXED EVENT HANDLERS
|
||||
@@ -122,9 +100,6 @@ const EnhancedMinimalHeader = ({
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleSecurityUpdate = (event) => {
|
||||
if (window.DEBUG_MODE) {
|
||||
console.log('🔒 Security level update event received:', event.detail);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setLastSecurityUpdate(0);
|
||||
@@ -132,9 +107,6 @@ const EnhancedMinimalHeader = ({
|
||||
};
|
||||
|
||||
const handleRealSecurityCalculated = (event) => {
|
||||
if (window.DEBUG_MODE) {
|
||||
console.log('🔐 Real security calculated event:', event.detail);
|
||||
}
|
||||
|
||||
if (event.detail && event.detail.securityData) {
|
||||
setRealSecurityLevel(event.detail.securityData);
|
||||
@@ -146,9 +118,6 @@ const EnhancedMinimalHeader = ({
|
||||
document.addEventListener('real-security-calculated', handleRealSecurityCalculated);
|
||||
|
||||
window.forceHeaderSecurityUpdate = (webrtcManager) => {
|
||||
if (window.DEBUG_MODE) {
|
||||
console.log('🔄 Force header security update called');
|
||||
}
|
||||
|
||||
if (webrtcManager && window.EnhancedSecureCryptoUtils) {
|
||||
window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager)
|
||||
@@ -156,11 +125,11 @@ const EnhancedMinimalHeader = ({
|
||||
if (securityData && securityData.isRealData !== false) {
|
||||
setRealSecurityLevel(securityData);
|
||||
setLastSecurityUpdate(Date.now());
|
||||
console.log('✅ Header security level force-updated');
|
||||
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Force update failed:', error);
|
||||
|
||||
});
|
||||
} else {
|
||||
setLastSecurityUpdate(0);
|
||||
@@ -178,53 +147,30 @@ const EnhancedMinimalHeader = ({
|
||||
// ============================================
|
||||
|
||||
React.useEffect(() => {
|
||||
const updateSessionInfo = () => {
|
||||
if (sessionManager) {
|
||||
const isActive = sessionManager.hasActiveSession();
|
||||
const timeLeft = sessionManager.getTimeLeft();
|
||||
const currentSession = sessionManager.currentSession;
|
||||
|
||||
setHasActiveSession(isActive);
|
||||
setCurrentTimeLeft(timeLeft);
|
||||
setSessionType(currentSession?.type || 'unknown');
|
||||
}
|
||||
};
|
||||
|
||||
updateSessionInfo();
|
||||
const interval = setInterval(updateSessionInfo, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [sessionManager]);
|
||||
// All security features are enabled by default - no session management needed
|
||||
setHasActiveSession(true);
|
||||
setCurrentTimeLeft(0);
|
||||
setSessionType('premium'); // All features enabled
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (sessionManager?.hasActiveSession()) {
|
||||
setCurrentTimeLeft(sessionManager.getTimeLeft());
|
||||
setHasActiveSession(true);
|
||||
} else {
|
||||
setHasActiveSession(false);
|
||||
setRealSecurityLevel(null);
|
||||
setLastSecurityUpdate(0);
|
||||
setSessionType('unknown');
|
||||
}
|
||||
}, [sessionManager, sessionTimeLeft]);
|
||||
// All security features are enabled by default
|
||||
setHasActiveSession(true);
|
||||
setCurrentTimeLeft(0);
|
||||
setSessionType('premium'); // All features enabled
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleForceUpdate = (event) => {
|
||||
if (sessionManager) {
|
||||
const isActive = sessionManager.hasActiveSession();
|
||||
const timeLeft = sessionManager.getTimeLeft();
|
||||
const currentSession = sessionManager.currentSession;
|
||||
|
||||
setHasActiveSession(isActive);
|
||||
setCurrentTimeLeft(timeLeft);
|
||||
setSessionType(currentSession?.type || 'unknown');
|
||||
}
|
||||
// All security features are enabled by default
|
||||
setHasActiveSession(true);
|
||||
setCurrentTimeLeft(0);
|
||||
setSessionType('premium'); // All features enabled
|
||||
};
|
||||
|
||||
// Connection cleanup handler (use existing event from module)
|
||||
const handleConnectionCleaned = () => {
|
||||
if (window.DEBUG_MODE) {
|
||||
console.log('🧹 Connection cleaned - clearing security data in header');
|
||||
}
|
||||
|
||||
|
||||
setRealSecurityLevel(null);
|
||||
setLastSecurityUpdate(0);
|
||||
@@ -235,110 +181,218 @@ const EnhancedMinimalHeader = ({
|
||||
};
|
||||
|
||||
const handlePeerDisconnect = () => {
|
||||
if (window.DEBUG_MODE) {
|
||||
console.log('👋 Peer disconnect detected - clearing security data in header');
|
||||
}
|
||||
|
||||
|
||||
setRealSecurityLevel(null);
|
||||
setLastSecurityUpdate(0);
|
||||
};
|
||||
|
||||
const handleDisconnected = () => {
|
||||
|
||||
setRealSecurityLevel(null);
|
||||
setLastSecurityUpdate(0);
|
||||
setHasActiveSession(false);
|
||||
setCurrentTimeLeft(0);
|
||||
setSessionType('unknown');
|
||||
};
|
||||
|
||||
document.addEventListener('force-header-update', handleForceUpdate);
|
||||
document.addEventListener('peer-disconnect', handlePeerDisconnect);
|
||||
document.addEventListener('connection-cleaned', handleConnectionCleaned);
|
||||
document.addEventListener('disconnected', handleDisconnected);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('force-header-update', handleForceUpdate);
|
||||
document.removeEventListener('peer-disconnect', handlePeerDisconnect);
|
||||
document.removeEventListener('connection-cleaned', handleConnectionCleaned);
|
||||
document.removeEventListener('disconnected', handleDisconnected);
|
||||
};
|
||||
}, [sessionManager]);
|
||||
}, []);
|
||||
|
||||
// ============================================
|
||||
// SECURITY INDICATOR CLICK HANDLER
|
||||
// ============================================
|
||||
|
||||
const handleSecurityClick = () => {
|
||||
if (!realSecurityLevel) {
|
||||
const handleSecurityClick = async (event) => {
|
||||
// Check if it's a right-click or Ctrl+click to disconnect
|
||||
if (event && (event.button === 2 || event.ctrlKey || event.metaKey)) {
|
||||
if (onDisconnect && typeof onDisconnect === 'function') {
|
||||
onDisconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent default behavior
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
|
||||
// Run real security tests if webrtcManager is available
|
||||
let realTestResults = null;
|
||||
if (webrtcManager && window.EnhancedSecureCryptoUtils) {
|
||||
try {
|
||||
realTestResults = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager);
|
||||
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
// If no real test results and no existing security level, show progress message
|
||||
if (!realTestResults && !realSecurityLevel) {
|
||||
alert('Security verification in progress...\nPlease wait for real-time cryptographic verification to complete.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use real test results if available, otherwise fall back to current data
|
||||
let securityData = realTestResults || realSecurityLevel;
|
||||
|
||||
// If still no security data, create a basic fallback
|
||||
if (!securityData) {
|
||||
securityData = {
|
||||
level: 'UNKNOWN',
|
||||
score: 0,
|
||||
color: 'gray',
|
||||
verificationResults: {},
|
||||
timestamp: Date.now(),
|
||||
details: 'Security verification not available',
|
||||
isRealData: false,
|
||||
passedChecks: 0,
|
||||
totalChecks: 0
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Detailed information about the REAL security check
|
||||
let message = `🔒 REAL-TIME SECURITY VERIFICATION\n\n`;
|
||||
message += `Security Level: ${realSecurityLevel.level} (${realSecurityLevel.score}%)\n`;
|
||||
message += `Session Type: ${realSecurityLevel.sessionType || 'demo'}\n`;
|
||||
message += `Verification Time: ${new Date(realSecurityLevel.timestamp).toLocaleTimeString()}\n`;
|
||||
message += `Data Source: ${realSecurityLevel.isRealData ? 'Real Cryptographic Tests' : 'Simulated Data'}\n\n`;
|
||||
let message = `REAL-TIME SECURITY VERIFICATION\n\n`;
|
||||
message += `Security Level: ${securityData.level} (${securityData.score}%)\n`;
|
||||
message += `Verification Time: ${new Date(securityData.timestamp).toLocaleTimeString()}\n`;
|
||||
message += `Data Source: ${securityData.isRealData ? 'Real Cryptographic Tests' : 'Simulated Data'}\n\n`;
|
||||
|
||||
if (realSecurityLevel.verificationResults) {
|
||||
if (securityData.verificationResults) {
|
||||
message += 'DETAILED CRYPTOGRAPHIC TESTS:\n';
|
||||
message += '=' + '='.repeat(40) + '\n';
|
||||
|
||||
const passedTests = Object.entries(realSecurityLevel.verificationResults).filter(([key, result]) => result.passed);
|
||||
const failedTests = Object.entries(realSecurityLevel.verificationResults).filter(([key, result]) => !result.passed);
|
||||
const passedTests = Object.entries(securityData.verificationResults).filter(([key, result]) => result.passed);
|
||||
const failedTests = Object.entries(securityData.verificationResults).filter(([key, result]) => !result.passed);
|
||||
|
||||
if (passedTests.length > 0) {
|
||||
message += '✅ PASSED TESTS:\n';
|
||||
message += 'PASSED TESTS:\n';
|
||||
passedTests.forEach(([key, result]) => {
|
||||
const testName = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
|
||||
message += ` ${testName}: ${result.details}\n`;
|
||||
message += ` ${testName}: ${result.details || 'Test passed'}\n`;
|
||||
});
|
||||
message += '\n';
|
||||
}
|
||||
|
||||
if (failedTests.length > 0) {
|
||||
message += '❌ UNAVAILABLE/Failed TESTS:\n';
|
||||
message += 'FAILED/UNAVAILABLE TESTS:\n';
|
||||
failedTests.forEach(([key, result]) => {
|
||||
const testName = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
|
||||
message += ` ${testName}: ${result.details}\n`;
|
||||
message += ` ${testName}: ${result.details || 'Test failed or unavailable'}\n`;
|
||||
});
|
||||
message += '\n';
|
||||
}
|
||||
|
||||
message += `SUMMARY:\n`;
|
||||
message += `Passed: ${realSecurityLevel.passedChecks}/${realSecurityLevel.totalChecks} tests\n`;
|
||||
message += `Passed: ${securityData.passedChecks}/${securityData.totalChecks} tests\n`;
|
||||
message += `Score: ${securityData.score}/${securityData.maxPossibleScore || 100} points\n\n`;
|
||||
}
|
||||
|
||||
// Add information about what is available in other sessions
|
||||
message += `\n📋 WHAT'S AVAILABLE IN OTHER SESSIONS:\n`;
|
||||
// Real security features status
|
||||
message += `SECURITY FEATURES STATUS:\n`;
|
||||
message += '=' + '='.repeat(40) + '\n';
|
||||
|
||||
if (realSecurityLevel.sessionType === 'demo') {
|
||||
message += `🔒 BASIC SESSION (5,000 sat - $2.00):\n`;
|
||||
message += ` • ECDSA Digital Signatures\n`;
|
||||
message += ` • Metadata Protection\n`;
|
||||
message += ` • Perfect Forward Secrecy\n`;
|
||||
message += ` • Nested Encryption\n`;
|
||||
message += ` • Packet Padding\n\n`;
|
||||
if (securityData.verificationResults) {
|
||||
const features = {
|
||||
'ECDSA Digital Signatures': securityData.verificationResults.verifyECDSASignatures?.passed || false,
|
||||
'ECDH Key Exchange': securityData.verificationResults.verifyECDHKeyExchange?.passed || false,
|
||||
'AES-GCM Encryption': securityData.verificationResults.verifyEncryption?.passed || false,
|
||||
'Message Integrity (HMAC)': securityData.verificationResults.verifyMessageIntegrity?.passed || false,
|
||||
'Perfect Forward Secrecy': securityData.verificationResults.verifyPerfectForwardSecrecy?.passed || false,
|
||||
'Replay Protection': securityData.verificationResults.verifyReplayProtection?.passed || false,
|
||||
'DTLS Fingerprint': securityData.verificationResults.verifyDTLSFingerprint?.passed || false,
|
||||
'SAS Verification': securityData.verificationResults.verifySASVerification?.passed || false,
|
||||
'Metadata Protection': securityData.verificationResults.verifyMetadataProtection?.passed || false,
|
||||
'Traffic Obfuscation': securityData.verificationResults.verifyTrafficObfuscation?.passed || false
|
||||
};
|
||||
|
||||
message += `🚀 PREMIUM SESSION (20,000 sat - $8.00):\n`;
|
||||
message += ` • All Basic + Enhanced features\n`;
|
||||
message += ` • Traffic Obfuscation\n`;
|
||||
message += ` • Fake Traffic Generation\n`;
|
||||
message += ` • Decoy Channels\n`;
|
||||
message += ` • Anti-Fingerprinting\n`;
|
||||
message += ` • Message Chunking\n`;
|
||||
message += ` • Advanced Replay Protection\n`;
|
||||
} else if (realSecurityLevel.sessionType === 'basic') {
|
||||
message += `🚀 PREMIUM SESSION (20,000 sat - $8.00):\n`;
|
||||
message += ` • Traffic Obfuscation\n`;
|
||||
message += ` • Fake Traffic Generation\n`;
|
||||
message += ` • Decoy Channels\n`;
|
||||
message += ` • Anti-Fingerprinting\n`;
|
||||
message += ` • Message Chunking\n`;
|
||||
message += ` • Advanced Replay Protection\n`;
|
||||
Object.entries(features).forEach(([feature, isEnabled]) => {
|
||||
message += `${isEnabled ? '✅' : '❌'} ${feature}\n`;
|
||||
});
|
||||
} else {
|
||||
// Fallback if no verification results
|
||||
message += `✅ ECDSA Digital Signatures\n`;
|
||||
message += `✅ ECDH Key Exchange\n`;
|
||||
message += `✅ AES-GCM Encryption\n`;
|
||||
message += `✅ Message Integrity (HMAC)\n`;
|
||||
message += `✅ Perfect Forward Secrecy\n`;
|
||||
message += `✅ Replay Protection\n`;
|
||||
message += `✅ DTLS Fingerprint\n`;
|
||||
message += `✅ SAS Verification\n`;
|
||||
message += `✅ Metadata Protection\n`;
|
||||
message += `✅ Traffic Obfuscation\n`;
|
||||
}
|
||||
|
||||
message += `\n${realSecurityLevel.details || 'Real cryptographic verification completed'}`;
|
||||
message += `\n${securityData.details || 'Real cryptographic verification completed'}`;
|
||||
|
||||
if (realSecurityLevel.isRealData) {
|
||||
if (securityData.isRealData) {
|
||||
message += '\n\n✅ This is REAL-TIME verification using actual cryptographic functions.';
|
||||
} else {
|
||||
message += '\n\n⚠️ Warning: This data may be simulated. Connection may not be fully established.';
|
||||
}
|
||||
|
||||
alert(message);
|
||||
// Show in a more user-friendly way
|
||||
const modal = document.createElement('div');
|
||||
modal.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.8);
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: monospace;
|
||||
`;
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.style.cssText = `
|
||||
background: #1a1a1a;
|
||||
color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
white-space: pre-line;
|
||||
border: 1px solid #333;
|
||||
`;
|
||||
|
||||
content.textContent = message;
|
||||
modal.appendChild(content);
|
||||
|
||||
// Close on click outside
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
document.body.removeChild(modal);
|
||||
}
|
||||
});
|
||||
|
||||
// Close on Escape key
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
document.body.removeChild(modal);
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
document.body.appendChild(modal);
|
||||
};
|
||||
|
||||
// ============================================
|
||||
@@ -399,9 +453,8 @@ const EnhancedMinimalHeader = ({
|
||||
};
|
||||
|
||||
const config = getStatusConfig();
|
||||
const displaySecurityLevel = realSecurityLevel || securityLevel;
|
||||
const displaySecurityLevel = isConnected ? (realSecurityLevel || securityLevel) : null;
|
||||
|
||||
const shouldShowTimer = hasActiveSession && currentTimeLeft > 0 && window.SessionTimer;
|
||||
|
||||
// ============================================
|
||||
// DATA RELIABILITY INDICATOR
|
||||
@@ -421,13 +474,13 @@ const EnhancedMinimalHeader = ({
|
||||
|
||||
if (isRealData) {
|
||||
return {
|
||||
tooltip: `${baseTooltip} - Real-time verification ✅`,
|
||||
tooltip: `${baseTooltip} - Real-time verification ✅\nRight-click or Ctrl+click to disconnect`,
|
||||
isVerified: true,
|
||||
dataSource: 'real'
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
tooltip: `${baseTooltip} - Estimated (connection establishing...)`,
|
||||
tooltip: `${baseTooltip} - Estimated (connection establishing...)\nRight-click or Ctrl+click to disconnect`,
|
||||
isVerified: false,
|
||||
dataSource: 'estimated'
|
||||
};
|
||||
@@ -441,18 +494,7 @@ const EnhancedMinimalHeader = ({
|
||||
// ============================================
|
||||
|
||||
React.useEffect(() => {
|
||||
window.debugHeaderSecurity = () => {
|
||||
console.log('🔍 Header Security Debug:', {
|
||||
realSecurityLevel,
|
||||
lastSecurityUpdate,
|
||||
isConnected,
|
||||
webrtcManagerProp: !!webrtcManager,
|
||||
windowWebrtcManager: !!window.webrtcManager,
|
||||
cryptoUtils: !!window.EnhancedSecureCryptoUtils,
|
||||
displaySecurityLevel: displaySecurityLevel,
|
||||
securityDetails: securityDetails
|
||||
});
|
||||
};
|
||||
window.debugHeaderSecurity = undefined;
|
||||
|
||||
return () => {
|
||||
delete window.debugHeaderSecurity;
|
||||
@@ -497,7 +539,7 @@ const EnhancedMinimalHeader = ({
|
||||
React.createElement('p', {
|
||||
key: 'subtitle',
|
||||
className: 'text-xs sm:text-sm text-muted hidden sm:block'
|
||||
}, 'End-to-end freedom. v4.01.222')
|
||||
}, 'End-to-end freedom v4.8.5')
|
||||
])
|
||||
]),
|
||||
|
||||
@@ -506,18 +548,17 @@ const EnhancedMinimalHeader = ({
|
||||
key: 'status-section',
|
||||
className: 'flex items-center space-x-2 sm:space-x-3'
|
||||
}, [
|
||||
// Session Timer
|
||||
shouldShowTimer && React.createElement(window.SessionTimer, {
|
||||
key: 'session-timer',
|
||||
timeLeft: currentTimeLeft,
|
||||
sessionType: sessionType,
|
||||
sessionManager: sessionManager
|
||||
}),
|
||||
|
||||
displaySecurityLevel && React.createElement('div', {
|
||||
key: 'security-level',
|
||||
className: 'hidden md:flex items-center space-x-2 cursor-pointer hover:opacity-80 transition-opacity duration-200',
|
||||
onClick: handleSecurityClick,
|
||||
onContextMenu: (e) => {
|
||||
e.preventDefault();
|
||||
if (onDisconnect && typeof onDisconnect === 'function') {
|
||||
onDisconnect();
|
||||
}
|
||||
},
|
||||
title: securityDetails.tooltip
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
@@ -583,7 +624,13 @@ const EnhancedMinimalHeader = ({
|
||||
displaySecurityLevel.color === 'yellow' ? 'bg-yellow-500/20' : 'bg-red-500/20'
|
||||
} ${securityDetails.isVerified ? '' : 'animate-pulse'}`,
|
||||
title: securityDetails.tooltip,
|
||||
onClick: handleSecurityClick
|
||||
onClick: handleSecurityClick,
|
||||
onContextMenu: (e) => {
|
||||
e.preventDefault();
|
||||
if (onDisconnect && typeof onDisconnect === 'function') {
|
||||
onDisconnect();
|
||||
}
|
||||
}
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
className: `fas fa-shield-alt text-sm ${
|
||||
@@ -607,7 +654,7 @@ const EnhancedMinimalHeader = ({
|
||||
React.createElement('span', {
|
||||
key: 'status-text',
|
||||
className: 'text-xs sm:text-sm font-medium'
|
||||
}, config.text)
|
||||
}, config.text),
|
||||
]),
|
||||
|
||||
// Disconnect Button
|
||||
|
||||