131 Commits
Author SHA1 Message Date
lockbitchat 026c81b775 chore: stop tracking node_modules
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Deploy Application / deploy (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
2026-05-17 18:03:10 -04:00
lockbitchat f71ff62417 fix: make WebRTC privacy mode explicit 2026-05-17 17:57:11 -04:00
lockbitchat ce48e8a851 fix: harden chat sanitization with DOMPurify 2026-05-17 17:52:36 -04:00
lockbitchat cc7f850e7d fix: bind SAS verification to DTLS fingerprint strings 2026-05-17 17:46:15 -04:00
lockbitchat 0a42aa13c3 release: prepare v4.8.5 security hardening release
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Deploy Application / deploy (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
2026-05-17 14:48:52 -04:00
lockbitchat 4b8c8829f1 Fix CSP errors, MIME types, and Service Worker issues
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Deploy Application / deploy (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
- Move CSP frame-ancestors and report-uri to HTTP headers
- Fix font-src to allow fonts.gstatic.com
- Add MIME type configuration for .jsx files
- Improve Service Worker error handling with cache fallback
- Rebuild application
2026-01-06 23:01:32 -04:00
lockbitchat ebcf2dcaac New component with partner logos
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Deploy Application / deploy (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
2025-12-30 04:16:41 -04:00
lockbitchat f136d0db3d Update macOS download link and bump version to 4.7.56
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Deploy Application / deploy (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
- Update macOS download URL to v0.1.0 DMG file
- Bump version from 4.7.55 to 4.7.56 across all files
- Rebuild project with updated version
2025-12-30 01:34:51 -04:00
lockbitchat 91c292a6cf feat: implement comprehensive PWA force update system
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Deploy Application / deploy (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
- Add UpdateManager and UpdateChecker for automatic version detection
- Add post-build script for meta.json generation and version injection
- Enhance Service Worker with version-aware caching
- Add .htaccess configuration for proper cache control

This ensures all users receive the latest version after deployment
without manual cache clearing.
2025-12-29 10:51:07 -04:00
lockbitchat 1b6431a36b Merge branch 'main' of https://github.com/SecureBitChat/securebit-chat
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
2025-12-28 20:30:12 -04:00
lockbitchat ee635da66f docs: Update README to v4.7.53 with Desktop Edition and roadmap updates
- Update version to v4.7.53
- Add Desktop Edition documentation (Windows, macOS, Linux)
- Update roadmap with all versions and dates
- Add mobile apps information (Q1 2026)
- Expand security and development sections
- Update URLs to securebit.chat domain
2025-12-28 20:28:22 -04:00
VolodymyrandGitHub 92907732bf Add Shared Security Core information to README
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
Added section on Shared Security Core and community contributions.
2025-12-23 13:59:24 -04:00
lockbitchat 968fff9c61 Project Update: Transition to Desktop & Mobile Versions
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
2025-11-04 14:14:30 -04:00
lockbitchat 79c8d08782 fix: prevent encryption key loss and IndexedDB connection errors
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
- Disable timer-based key rotation for Double Ratchet mode
- Auto-reinitialize encryption keys when missing but ECDH available
- Preserve active keys during periodic cleanup in ratchet sessions
- Fix IndexedDB "database closing" errors with connection checking
- Add individual transactions per queue item to prevent race conditions
2025-11-04 13:14:24 -04:00
lockbitchat 207e51361c security: increase PBKDF2 iterations from 100,000 → 310,000 (OWASP 2025 compliance)
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
Updated PBKDF2 key derivation parameters to align with OWASP 2025 recommendations.
PBKDF2-HMAC-SHA256 now uses 310,000 iterations instead of 100,000 to improve resistance
against modern GPU and ASIC brute-force attacks.

- Updated both encryptData() and decryptData() derivation routines.
- Ensures ~100ms derivation time on modern CPUs (meets OWASP 2025 standard).
- No changes required for backward compatibility of existing ciphertexts.
2025-10-30 15:24:09 -04:00
lockbitchat 4583db39a2 feat(security): switch master key to non-extractable CryptoKey handle and remove direct access
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
2025-10-30 15:16:36 -04:00
lockbitchat 77ed4b3e4f remove key/hash logging and debug output from EnhancedSecureWebRTCManager and EnhancedSecureCryptoUtils
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
2025-10-30 14:55:46 -04:00
lockbitchat c7b16157fc Enhanced HKDF-based key derivation with improved security features
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
- Implemented proper RFC 5869 compliant HKDF key derivation process
- Added Perfect Forward Secrecy (PFS) key for enhanced session security
- Improved key separation using unique info parameters for each derived key
- Enhanced salt size from 32 to 64 bytes for increased entropy
- Added comprehensive key validation and error handling
- Implemented proper ECDH + HKDF integration following Web Crypto API best practices
- Added metadata encryption key for enhanced data protection
- Improved compatibility with modern cryptographic standards (RFC 7748, NIST SP 800-56A)
 -Enhanced logging and debugging capabilities for cryptographic operations
- Maintained backward compatibility while upgrading security infrastructure
Security improvements:
- Cryptographic isolation between different key purposes
- Enhanced protection against cross-key attacks
- Improved resistance to future key compromise scenarios
- Better compliance with OWASP cryptographic storage guidelines
Technical details:
- Refactored deriveSharedKeys() method for proper HKDF implementation
- Updated WebRTC manager to use new messageKey API
- Added comprehensive error handling and validation
- Improved browser compatibility with standardized cryptographic operations
- This update strengthens the existing security foundation with modern cryptographic practices while maintaining full system compatibility.
2025-10-27 15:18:15 -04:00
lockbitchat 3c2bac588c ci: switch mirror workflow to HTTPS token authentication
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled
2025-10-20 12:51:27 -04:00
lockbitchat 2ae65eb749 ci: switch mirror workflow to HTTPS token authentication 2025-10-20 12:44:59 -04:00
lockbitchat 4a3249b2a6 test 2025-10-20 12:39:55 -04:00
lockbitchat 456545f055 Add GitHub Actions workflow to mirror repository to PrivacyGuides 2025-10-20 12:34:10 -04:00
lockbitchat 40381cc0a1 Increase session timeout to 60min and inactivity to 30min
Fix sendMessage isUnlocked check to prevent immediate lock
Move session extension logic to SecureMasterKeyManager
Improve error messages
Keep validation of crypto keys before encryption
2025-10-20 02:06:42 -04:00
lockbitchat 0d7835cfa2 fix: resolve incomplete multi-character sanitization in sanitizeMessage
Use stable replacement loop for all multi-character patterns to prevent malicious input from reappearing after sanitization.
2025-10-20 01:20:02 -04:00
lockbitchat 7604381584 fix: eliminate division on crypto random in getSafeRandomFloat 2025-10-20 01:10:25 -04:00
lockbitchat a7a5861e0a fix: replace modulo with bitwise mask in crypto random 2025-10-20 01:02:07 -04:00
lockbitchat 63a19e6a4c fix: resolve CodeQL biased crypto random warning
Use unsigned right shift (>>>) to properly handle 32-bit random values and eliminate statistical bias.
2025-10-20 00:51:03 -04:00
lockbitchat b18e943abd Fix cryptographic random bias in fingerprint mask generation 2025-10-20 00:42:12 -04:00
lockbitchat 9c1258cd1e Fix cryptographic random bias in fingerprint mask generation 2025-10-20 00:42:03 -04:00
lockbitchat 60e4bb6b8a Fix cryptographic random bias in getUnbiasedRandomInRange
Reworked getUnbiasedRandomInRange() to eliminate modulo bias by using
rejection sampling combined with bucket indexing instead of the `%` operator.
Added getUnbiasedRandomFloat() to safely generate unbiased floats for
sizeVariation and sizeMultiplier.
2025-10-20 00:34:17 -04:00
lockbitchat 4233ba3d7e Fix cryptographic random bias (CodeQL js/biased-cryptographic-random)
Reimplemented getUnbiasedRandomInRange() using rejection sampling for all range sizes to eliminate modulo bias and ensure uniform cryptographically secure randomness.
2025-10-20 00:25:50 -04:00
lockbitchat 333c4b87b5 fix: improve sanitization to prevent XSS
- Now the sanitization function protects against:
  - Nested script tags: <scrip<script>alert("XSS")</script>t>
  - HTML comments containing scripts: <!-- <script>alert("XSS")</script> -->
  - Multiple overlapping tags: <script><script>alert("XSS")</script></script>
  - Attributes in closing tags: </script foo="bar">
  - Complex nested structures combining different tags
  - All known XSS vectors
2025-10-19 23:55:41 -04:00
lockbitchat 5096a09811 fix: improve sanitization to prevent XSS
- Now the sanitization function protects against:
  - Nested script tags: <scrip<script>alert("XSS")</script>t>
  - HTML comments containing scripts: <!-- <script>alert("XSS")</script> -->
  - Multiple overlapping tags: <script><script>alert("XSS")</script></script>
  - Attributes in closing tags: </script foo="bar">
  - Complex nested structures combining different tags
  - All known XSS vectors
2025-10-19 23:54:26 -04:00
lockbitchat 5c658cdd6c Security updates for CodeQL 2025-10-19 23:39:14 -04:00
lockbitchat e209d3e6f5 fix: improve sanitization to prevent XSS
- Now the sanitization function protects against:
  - Nested script tags: <scrip<script>alert("XSS")</script>t>
  - HTML comments containing scripts: <!-- <script>alert("XSS")</script> -->
  - Multiple overlapping tags: <script><script>alert("XSS")</script></script>
  - Attributes in closing tags: </script foo="bar">
  - Complex nested structures combining different tags
  - All known XSS vectors
2025-10-19 23:23:58 -04:00
lockbitchat 49e5a054cb fix: security and build improvements
- Fixed biased cryptographic random with modulo operations
- Corrected biased cryptographic random with addition operations
- Resolved infinite page loading issue
- Rebuilt all distribution files
2025-10-19 23:05:24 -04:00
lockbitchat 9d0372484a Add CodeQL security scanning workflow 2025-10-19 22:08:31 -04:00
lockbitchat 69fa5ffc28 Add CodeQL security scanning workflow 2025-10-19 22:04:07 -04:00
lockbitchat ed169b4c8c Add CodeQL security scanning workflow 2025-10-19 22:02:28 -04:00
lockbitchat 906562333e fix(ios): prevent chat crash when Notifications API is unavailable on iPhones
- Guarded all Notification API usage to avoid ReferenceError on iOS Safari.
- Set default permission to 'denied' when Notification is undefined.
- Added early return in notification flow when Notifications API is unavailable.
- Wrapped Notification.permission, requestPermission(), and new Notification(...) with typeof checks.
- Updated SecureNotificationManager and app.jsx to degrade gracefully.
- Verified build passes and chat loads correctly on iOS without notifications.
2025-10-19 20:51:44 -04:00
lockbitchat 4e7f5867b5 feat: implement automatic key generation on channel creation
- Remove manual "Create secure keys" button requirement
- Add automatic key generation trigger when "Create channel" is clicked
- Implement loading state with spinner animation during key generation
- Add isGeneratingKeys state management across components
- Pass handleCreateOffer function as prop to EnhancedConnectionSetup
- Update UI to show "Generating secure keys..." message during process
- Ensure proper state cleanup on disconnect and data clearing
- Improve user experience by eliminating extra click step in channel creation flow
2025-10-19 15:23:02 -04:00
lockbitchat 5ddfd1f5b3 fix(ios): prevent chat crash when Notifications API is unavailable on iPhones
- Guarded all Notification API usage to avoid ReferenceError on iOS Safari.
- Set default permission to 'denied' when Notification is undefined.
- Added early return in notification flow when Notifications API is unavailable.
- Wrapped Notification.permission, requestPermission(), and new Notification(...) with typeof checks.
- Updated SecureNotificationManager and app.jsx to degrade gracefully.
- Verified build passes and chat loads correctly on iOS without notifications.
2025-10-17 03:49:33 -04:00
lockbitchat 1acbc12a92 update version to v4.4.18 2025-10-15 20:23:50 -04:00
lockbitchat 3ed8766fc9 v4.3.120 update 2025-10-15 20:15:41 -04:00
lockbitchat b087adfecc feat: implement secure browser notifications system
- Added SecureNotificationManager with cross-browser support (Chrome, Firefox, Safari, Edge)
- Integrated WebRTC message notifications with tab visibility detection
- Implemented XSS protection, URL validation, and rate limiting
- Notifications shown only when chat tab is inactive
- Enforced HTTPS and user gesture requirements
2025-10-15 19:58:28 -04:00
lockbitchat 5b5cc67fdc Push to Codeberg 2025-10-15 04:55:22 -04:00
lockbitchat 93e4b7ad19 test 2025-10-15 04:51:48 -04:00
lockbitchat fce18d27a5 Push to PrivacyGuides 2025-10-15 04:41:05 -04:00
lockbitchat 03e06f59dc Push to Codeberg 2025-10-15 04:30:18 -04:00
lockbitchat 5df6cb0d16 test ssh 2025-10-15 04:25:49 -04:00
lockbitchat 3730f46a08 test remote 2025-10-15 04:23:51 -04:00
lockbitchat abd6f9a7f5 Test mirror 2025-10-15 04:21:20 -04:00
lockbitchat 99469e5bd5 Remove experimental Bluetooth key transfer module
- Deleted BluetoothKeyTransfer.js and related classes
- Removed BluetoothKeyTransfer.jsx UI component
- Cleaned up Bluetooth imports from app-boot.js and bootstrap-modules.js
- Removed Bluetooth buttons and handlers from main app
- Eliminated all Bluetooth functionality due to Web Bluetooth API limitations
- Browsers cannot create GATT servers or advertise devices
- Reduced bundle size by ~78KB
- Application now focuses on supported browser technologies (QR codes, manual key exchange, WebRTC)
2025-10-15 04:13:14 -04:00
lockbitchat 2306b32482 Remove experimental Bluetooth key transfer module
- Deleted BluetoothKeyTransfer.js and related classes
- Removed BluetoothKeyTransfer.jsx UI component
- Cleaned up Bluetooth imports from app-boot.js and bootstrap-modules.js
- Removed Bluetooth buttons and handlers from main app
- Eliminated all Bluetooth functionality due to Web Bluetooth API limitations
- Browsers cannot create GATT servers or advertise devices
- Reduced bundle size by ~78KB
- Application now focuses on supported browser technologies (QR codes, manual key exchange, WebRTC)
2025-10-15 01:47:28 -04:00
lockbitchat cbb6a8fa31 Remove experimental Bluetooth key transfer module
- Deleted BluetoothKeyTransfer.js and related classes
- Removed BluetoothKeyTransfer.jsx UI component
- Cleaned up Bluetooth imports from app-boot.js and bootstrap-modules.js
- Removed Bluetooth buttons and handlers from main app
- Eliminated all Bluetooth functionality due to Web Bluetooth API limitations
- Browsers cannot create GATT servers or advertise devices
- Reduced bundle size by ~78KB
- Application now focuses on supported browser technologies (QR codes, manual key exchange, WebRTC)
2025-10-15 01:46:54 -04:00
lockbitchat 4be6fc55f5 Remove experimental Bluetooth key transfer module
- Deleted BluetoothKeyTransfer.js and related classes
- Removed BluetoothKeyTransfer.jsx UI component
- Cleaned up Bluetooth imports from app-boot.js and bootstrap-modules.js
- Removed Bluetooth buttons and handlers from main app
- Eliminated all Bluetooth functionality due to Web Bluetooth API limitations
- Browsers cannot create GATT servers or advertise devices
- Reduced bundle size by ~78KB
- Application now focuses on supported browser technologies (QR codes, manual key exchange, WebRTC)
2025-10-15 01:46:36 -04:00
lockbitchat cb62d8eb3b fix: resolve ReferenceError issues and improve disconnect functionality
- Add missing sessionTimeLeft state variable to fix ReferenceError in handleDisconnect
- Fix ReferenceError in handleCreateAnswer by correcting variable scope (e.target.value -> answerInput)
- Add type checking for answerData.trim() to prevent TypeError in shouldPreserveAnswerData
- Standardize data types by replacing null assignments with empty strings for offerData/answerData
- Improve handleDisconnect function with proper error handling and comprehensive state cleanup
- Add try-catch wrapper around disconnect operations for better error handling
- Ensure complete metadata removal and connection termination on disconnect
- Fix Bluetooth transfer modal opening with proper role-based event dispatching
2025-10-14 22:51:48 -04:00
lockbitchat ea8cf47118 Table update 2025-10-13 11:54:05 -04:00
lockbitchat d292c84829 session bug fix 2025-10-13 11:13:11 -04:00
lockbitchat 45de8ce285 refactor: implement minimal PWA caching strategy
- Cache only essential PWA assets (manifest, icons, core scripts)
- Use Network First for all other requests
- Remove aggressive caching of UI components and styles
- Preserve PWA installation while minimizing cache footprint
2025-10-13 01:38:41 -04:00
lockbitchat 7af8f528ff refactor: implement minimal PWA caching strategy
- Cache only essential PWA assets (manifest, icons, core scripts)
- Use Network First for all other requests
- Remove aggressive caching of UI components and styles
- Preserve PWA installation while minimizing cache footprint
2025-10-13 01:35:32 -04:00
lockbitchat d24d578321 feat: Add comprehensive iOS PWA splash screen support
- Add complete splash screen configuration for all iOS devices
- Support iPhone 17 Pro Max through iPhone 6 series
- Support all iPad models with landscape/portrait orientations
- Use proper media queries and generated splash images
- Fix iOS splash screen caching and display issues
2025-10-09 20:49:04 -04:00
lockbitchat dd9f13ab59 Add Browser Extension block 2025-10-09 05:09:17 -04:00
lockbitchat 2e5b078bc2 UX/UI updates and bug fixes 2025-10-08 17:23:54 -04:00
lockbitchat 75fa1cd27d Qr generator create bug fix 2025-10-08 01:24:04 -04:00
lockbitchat 60db79d2ae v4.3.120 update 2025-10-08 00:17:53 -04:00
lockbitchat 204e1edd9a Update version to 4.3.120 UX/UI 2025-10-08 00:08:13 -04:00
lockbitchat 5b557102b4 Fix QR scanner multi-chunk processing and binary data handling
- Fix duplicate chunk detection by using data hash instead of index
- Add comprehensive logging for QR scanner debugging
- Implement proper buffer cleanup when scanner is closed
- Preserve original binary data instead of decoding to JSON
- Add deduplication logic to prevent same QR code being processed multiple times
- Improve error handling and scanner state management
- Fix binary chunk reconstruction to maintain SB1:bin: prefix format
2025-10-07 23:58:54 -04:00
lockbitchat e6d6576d70 Render loading state if not ready 2025-10-06 14:35:13 -04:00
lockbitchat d2830b9c46 Optimize JSON and QR codes
- Replaced original JSON with minimized binary format (gzip + base64).
- Adjusted rendering and QR code generation for compatibility.
- Reduced payload size for improved efficiency.
2025-10-05 06:21:14 -04:00
lockbitchat ec04bebf22 Update UX/UI slider 2025-10-03 00:07:11 -04:00
lockbitchat 8cd78a2aba Update slider UX/UI 2025-10-02 21:34:45 -04:00
lockbitchat 4359e5fab1 Add Testimonials section with user feedback 2025-10-02 19:39:40 -04:00
lockbitchat 78769b58ef UX/UI update 2025-10-02 16:58:09 -04:00
lockbitchat afc873761f update 2025-10-02 16:52:42 -04:00
lockbitchat 7682f57889 feat(ui): add glowing border effect and improve card hover interactions
- implemented glowing border effect on cards when cursor is near
- multiple cards can glow simultaneously within proximity
- smooth fade-out without flicker on mouse leave
- improved hover animation for better user experience
2025-10-02 16:52:31 -04:00
lockbitchat 6bed472e09 feat(security): implement core crypto and logging hardening
- removed all logging of raw IV, JWK, session tokens, verification codes
  → logs now only include error codes, timestamps and short non-reversible SHA-256 hashes (first 4 bytes)
- replaced global master key storage with PBKDF2-derived non-extractable AES key
  → master key lives only in session memory and is cleared on timeout/focus-out
- added password-based derivation (PBKDF2) for master key initialization
- migrated persistent key storage to AES-GCM wrapped ciphertext in IndexedDB
  → JWK export → encrypt with session master key → store ciphertext + IV + metadata
  → unwrap + import as non-extractable on restore
- removed busy-wait loops and direct `window.gc()` calls
  → replaced with async non-blocking cleanup via setTimeout/Promise/WebWorker
2025-10-02 03:25:38 -04:00
lockbitchat 65cc136b99 chore: remove debug logging and disable debug mode for production
- Removed temporary console logs used for debugging
- Disabled DEBUG_MODE flag
- Updated configuration to run in production mode
- Cleaned up leftover debug utilities to reduce noise in console
2025-10-02 01:43:32 -04:00
lockbitchat 5ce6db1640 update 2025-10-01 23:27:59 -04:00
lockbitchat 0dde4906d9 update UX/UI 2025-10-01 23:26:07 -04:00
lockbitchat 0a7a830b52 Update UX/UI landing page 2025-10-01 20:20:15 -04:00
lockbitchat 49347b5c78 Uopdated version to v4.2.12 - ECDH + DTLS + SAS 2025-09-30 14:18:01 -04:00
lockbitchat 85d900727d Update v4.2.12 2025-09-30 14:13:23 -04:00
lockbitchat 7902359c48 feat(qr-exchange): improved QR code exchange system
- Updated connection flow between users via QR codes
- Added manual switching option in QR code generator
- Increased number of QR codes for better readability
2025-09-27 19:07:17 -04:00
lockbitchat 0ce05b836b Update delete old laying visual files 2025-09-24 10:48:32 -04:00
lockbitchat 34094956b7 feat(core): update session, security system and QR exchange
- Removed session creation and Lightning payment logic
- Refactored security system:
  * no more restrictions
  * all systems enabled on session creation
- Improved QR code exchange for mobile devices
2025-09-23 20:01:02 -04:00
lockbitchat 804b384271 feat: implement build system and development workflow
- Add npm scripts for CSS/JS compilation (build:css, build:js, build)
- Create PowerShell build automation script
- Document development workflow in README
- Add troubleshooting guide for build issues
- Specify proper file structure and compilation process

Supports Tailwind CSS v3.4.0 and esbuild bundling with source maps.
2025-09-08 19:22:50 -04:00
lockbitchat 5034386ee6 Update 2025-09-08 16:40:39 -04:00
lockbitchat 14b6ae2337 Add to CSP
report-uri /csp-report;
report-to csp-endpoint;">
2025-09-08 16:10:26 -04:00
lockbitchat 0f8399ec88 feat(security,ui): self-host React deps, Tailwind, fonts; strict CSP; local QR; better selection state
Replace CDN React/ReactDOM/Babel with local libs; remove Babel and inline scripts
Build Tailwind locally, add safelist; switch to assets/tailwind.css
Self-host Font Awesome and Inter (CSS + woff2); remove external font CDNs
Implement strict CSP (no unsafe-inline/eval; scripts/styles/fonts from self)
Extract inline handlers; move PWA scripts to external files
Add local QR code generation (qrcode lib) and remove api.qrserver.com
Improve SessionTypeSelector visual selection (highlighted background and ring)
Keep PWA working with service worker and offline assets
Refs: CSP hardening, offline-first, no external dependencies
2025-09-08 16:04:58 -04:00
lockbitchat 3458270477 deleting unnecessary comments 2025-09-08 11:45:32 -04:00
lockbitchat e2316f6557 **What Changed:**
- **Removed:** All libsodium dependencies and PAKE-based authentication
- **Replaced With:** ECDH + DTLS + SAS triple-layer security system
- **Impact:** Eliminates complex PAKE implementation in favor of standardized protocols

**Security Benefits:**
-  **Simplified Architecture** - Reduced attack surface
-  **Standards Compliance** - RFC-compliant protocols
-  **Better Maintenance** - Native Web Crypto API usage
-  **Enhanced Security** - Triple-layer defense system

**New Features:**
- **Elliptic Curve Diffie-Hellman** using P-384 (secp384r1)
- **Cryptographically secure** key pair generation
- **Perfect Forward Secrecy** with session-specific keys
- **MITM resistance** requiring knowledge of both private keys
2025-09-04 17:25:01 -04:00
lockbitchat 0d029f5d39 delet test_method 2025-09-01 17:14:16 -04:00
lockbitchat beb5bcfd01 fix: resolve message sending - _createMessageAAD method not found
- Move methods to constructor for early availability
- Add connectionId initialization
- Remove duplicate definitions
- Fix AAD creation for anti-replay protection
2025-09-01 17:14:04 -04:00
lockbitchat 77c19c4d71 CRITICAL: Fix major security vulnerabilities
- Remove forced session verification bypass (MITM)
- Implement mandatory file message encryption with AAD
- Add DTLS fingerprint validation and SDP parsing
- Implement hard security gate for unverified connections
- Add emergency key wipe on security breaches
2025-09-01 16:04:33 -04:00
lockbitchat a04fc16d58 Security Update 2025-09-01 15:15:42 -04:00
lockbitchat 14b001710a feat(security): strengthen crypto, entropy checks, and memory cleanup
- Added constant-time key validation to prevent timing attacks
- Improved randomization (crypto.getRandomValues, larger noise patterns)
- Optimized IV cleanup with batch processing and shorter lifetime
- Added multi-dimensional entropy analysis with quantum-resistant checks
- Enhanced emergency cleanup with atomic operations and rollback
2025-08-31 19:37:26 -04:00
lockbitchat 35d83d46f2 feat(security): Implement input validation and fix logging conflicts
- Added comprehensive input validation with size/rate limits
- Fixed logging conflicts by renaming _sanitizeString → _sanitizeInputString
- Implemented XSS/injection protection via pattern detection
- Added safe logging recovery mechanism
- Maintained all existing security protections
2025-08-31 18:35:31 -04:00
lockbitchat 6c4d0eeaee feat(security): harden API export, remove global flags, unify scheduler
- Removed reliance on window.DEBUG_MODE and window.DISABLE_* flags.
- Configuration is now passed via constructor instead of global variables.
- Exposed API remains on `window.secureBitChat`, but without backup globals or hidden toggles.
- Consolidated multiple setInterval tasks into a single scheduler to reduce attack surface.
- Added strict limits for IV history and log storage to prevent memory exhaustion.
- Improved XSS hardening: no more global switches to disable security features.
2025-08-31 18:01:46 -04:00
lockbitchat 39ae9f01b7 feat: comprehensive security fixes for critical vulnerabilities
BREAKING CHANGES:
- Enhanced logging system with comprehensive data sanitization
- Atomic key generation with race condition protection
- Strengthened mutex system implementation
- Complete IV reuse prevention system
- Secured global API with multi-layer protection
- Enhanced memory management with secure wiping
- Improved error handling without information disclosure

Security Fixes:
• CRITICAL: Fix logging system data leakage
  - Add comprehensive pattern detection (hex, base64, base58, base32)
  - Implement entropy-based sensitive data detection
  - Add suspicious character distribution analysis
  - Emergency disable logging on security violations
  - Reduce production logging to errors only

• CRITICAL: Resolve race conditions in key generation
  - Implement atomic state checks within mutex protection
  - Add proper waiting mechanism for concurrent operations
  - Validate key generation results before assignment
  - Add operation timeouts and error recovery

• HIGH: Strengthen mutex system implementation
  - Replace silent failures with proper error propagation
  - Add comprehensive mutex system validation
  - Implement authorized emergency unlock mechanisms
  - Enhanced timeout handling with state consistency
  - Add mutex diagnostics and recovery systems

• HIGH: Complete IV reuse prevention system
  - Implement comprehensive IV collision detection
  - Add entropy validation for generated IVs
  - Track IV usage across sessions with cleanup
  - Detect suspicious patterns and weak RNG
  - Emergency mode activation on security violations

• HIGH: Secure global API exposure
  - Multi-layer protection with Proxy-based access control
  - Block internal property access attempts
  - Implement API replacement monitoring and restoration
  - Add method interception protection
  - Comprehensive global namespace monitoring

• HIGH: Enhanced memory management
  - Implement secure memory wiping for all data types
  - Multiple overwrite passes (random, zeros, ones)
  - Secure cleanup of cryptographic materials
  - Force garbage collection where available
  - Track and validate memory cleanup operations

• HIGH: Improve error handling security
  - Categorize errors by security sensitivity
  - Generate safe error messages without internal details
  - Track error frequency for security monitoring
  - Implement secure error message mapping

Additional Security Enhancements:
• Add DTLS ClientHello race condition protection
• Implement enhanced SecureKeyStorage with encryption
• Add comprehensive IV tracking and validation systems
• Create emergency mode systems for security violations
• Enhance cryptographic validation and state management
• Add extensive security diagnostics and monitoring

Performance & Reliability:
• Optimize mutex usage to reduce contention
• Improve error recovery mechanisms
• Add comprehensive system validation
• Enhanced debugging and diagnostic capabilities
• Better resource cleanup and management
2025-08-31 15:52:34 -04:00
lockbitchat 551feb21a4 Security hardening: remove global leaks, enforce crypto, cleanup logs
- Removed unsafe exposure of internal objects:
      • deleted all references to `window.fileTransferSystem`
      • unified global API setup to a single immutable `safeGlobalAPI` via Object.freeze
      • removed legacy branch with `delete window[name]`

    - Logging improvements:
      • eliminated all direct `console.*` calls (use `_secureLog` exclusively)
      • fixed string templates with proper interpolation (``${var}``)
      • prevented accidental fallback to raw console in production

    - Cryptography enforcement:
      • updated `_validateCryptographicSecurity()` to require ECDSA and PFS
      • added emergency shutdown if critical crypto features are missing
      • ensured consistent feature enabling for production
2025-08-28 18:53:16 -04:00
lockbitchat a265209ff6 fix(security): fixes in keystore and cryptography
- Metadata of extracted keys is now marked encrypted: true
- Removed decryption bypass via _keyReferences, all keys are returned via decrypt
- Nested encryption uses random 96-bit IV instead of single byte increment
- Fixed _secureLogShim: correct argument destructuring (...args)
- Removed busy-wait in forceInitializeFileTransfer, replaced with async wait
2025-08-28 17:01:14 -04:00
lockbitchat 97b87828e2 feat(security): encrypted key storage with WeakMap and SecureKeyStorage
- Added SecureKeyStorage class: JWK storage encrypted (AES-GCM) + WeakMap
- Support for non-extractable keys via secure links without export
- Implemented secure wipe/secure wipe all, statistics collection
- Improved secure logging: blacklist of sensitive fields, whitelist of secure ones
- Integration with WebRTC manager layers and secure key installation
2025-08-28 16:17:40 -04:00
lockbitchat a4161bc47e SECURITY: Fix console logging vulnerabilities in production mode
- Replace unsafe console.error/warn calls with secure logging system
- Implement production-safe logging that masks sensitive data
- Add automatic sanitization for cryptographic keys and sensitive information
- Create secure logging infrastructure for EnhancedSecureWebRTCManager and EnhancedSecureCryptoUtils
- Prevent data leakage of internal logic and error details in production
- Add production/development mode detection for appropriate logging levels
- Implement server-side error logging for production environments
2025-08-28 15:15:09 -04:00
lockbitchat e468838596 SECURITY: Fix console logging vulnerabilities in production mode
- Replace unsafe console.error/warn calls with secure logging system
- Implement production-safe logging that masks sensitive data
- Add automatic sanitization for cryptographic keys and sensitive information
- Create secure logging infrastructure for EnhancedSecureWebRTCManager and EnhancedSecureCryptoUtils
- Prevent data leakage of internal logic and error details in production
- Add production/development mode detection for appropriate logging levels
- Implement server-side error logging for production environments
2025-08-28 15:05:42 -04:00
lockbitchat 398d8bc014 Updated application documentation and website homepage to include ASN.1 Validation 2025-08-27 13:25:26 -04:00
lockbitchat 6aaabbd1df feat(security): Implement full ASN.1 validation for key structure verification
BREAKING CHANGE: Enhanced key validation now performs complete ASN.1 parsing

Security improvements:
- Added complete ASN.1 DER parser for full structure validation
- Implemented OID validation for algorithms and curves (P-256/P-384 only)
- Added EC point format verification (uncompressed format 0x04)
- Validate SPKI structure elements count and types
- Check key size limits to prevent DoS attacks (50-2000 bytes)
- Verify unused bits in BIT STRING (must be 0)
- Added fallback support from P-384 to P-256

This fixes high-risk vulnerability where keys with valid headers but
modified data could be accepted. Now all structural elements are validated
according to PKCS standards.

Affected methods:
- validateKeyStructure() - complete rewrite with ASN.1 parsing
- All key import/export methods now use enhanced validation
2025-08-27 12:39:18 -04:00
lockbitchat 0b01083fce Deleting a test method 2025-08-26 21:36:52 -04:00
lockbitchat 984564fae6 feat: add enhanced security validation and timing protection
Security Enhancements:
- Add salt entropy validation (minimum 16 unique bytes for 64-byte salts)
- Expand secure logging patterns to catch more sensitive data types
  (fingerprint, mac, hash patterns)
- Implement random timing delays in verifyAuthProof() to mask execution timing
- Revert sanitizeMessage() to working regex-based approach

Validation Improvements:
- validateSalt() now checks both size and entropy quality
- secureLog.sanitizeContext() enhanced with additional sensitive patterns
- Added timing attack protection with 5-25ms random delays
2025-08-26 19:58:45 -04:00
lockbitchat 1e270fb4b8 security: fix critical vulnerabilities in crypto module
- Remove insecure key import backdoor
- Strengthen password generation (32 chars + special chars)
- Implement constant-time comparisons to prevent timing attacks
- Fix race conditions in rate limiting with atomic operations
- Add input validation and enhanced error handling

BREAKING CHANGE: Remove allowInsecureImport option - all signed packages
now require mandatory signature verification for security.
2025-08-26 19:44:34 -04:00
lockbitchat e7c6dfc3b3 feat: Implement comprehensive token-based authentication system
Add complete Web3-powered token authentication module for SecureBit project

- **TokenAuthManager.js**: Main authentication manager handling Web3 wallet connections,
  session creation/validation, and automatic session termination
- **Web3ContractManager.js**: Smart contract interface for token operations and validation
- **SecureBitAccessToken.sol**: ERC-721 smart contract for access tokens with monthly/yearly durations

- **TokenAuthModal.jsx**: User interface for wallet connection and token purchase
- **TokenStatus.jsx**: Header component displaying token status and remaining time

- ERC-721 compliant access tokens with configurable durations (1 month/1 year)
- OpenZeppelin security contracts integration (Ownable, ReentrancyGuard, Pausable)
- Token purchase, renewal, and deactivation functionality
- Automatic expiry validation and price management
- Transfer handling with user token tracking
- Pausable functionality for emergency contract control

- `purchaseMonthlyToken()` / `purchaseYearlyToken()`: Token acquisition
- `isTokenValid()`: Real-time token validation
- `renewToken()`: Token extension functionality
- `deactivateToken()`: Manual token deactivation
- `getTokenPrices()`: Dynamic pricing information
- `pause()` / `unpause()`: Emergency control functions

- Web3 signature verification for wallet ownership
- Single active session enforcement per account
- Automatic session termination on new device login
- Cryptographic signature validation
- MITM and replay attack protection preservation
- Blockchain-based token validation

- Modular architecture for easy integration
- Web3.js integration for Ethereum network interaction
- MetaMask wallet support
- Session heartbeat monitoring
- Automatic token expiry handling
- Comprehensive error handling and logging

- src/token-auth/TokenAuthManager.js
- src/token-auth/Web3ContractManager.js
- src/token-auth/SecureBitAccessToken.sol
- src/token-auth/config.js
- src/components/ui/TokenAuthModal.jsx
- src/components/ui/TokenStatus.jsx

- Smart contract includes comprehensive test scenarios
- Mock mode available for development testing
- Hardhat deployment scripts provided
2025-08-24 23:56:12 -04:00
lockbitchat dde7196bb8 fix: prevent install prompt showing in installed PWA
- Improve installation status detection logic
- Add proper DOM cleanup when PWA is installed
- Enhance monitoring for installation state changes
- Fix shouldShowPrompt() logic to always check current status
- Add forceInstallationCheck() method for manual status updates
2025-08-24 17:07:31 -04:00
lockbitchat 26ba6eebb9 fix: prevent install prompt showing in installed PWA
- Improve installation status detection logic
- Add proper DOM cleanup when PWA is installed
- Enhance monitoring for installation state changes
- Fix shouldShowPrompt() logic to always check current status
- Add forceInstallationCheck() method for manual status updates
2025-08-24 17:04:01 -04:00
lockbitchat 171a7d9dfb Fixed DTLS Race Condition & Memory Safety
 FIXED HIGH CRITICALITY vulnerabilities (October 2024):
- DTLS ClientHello Race Condition: Added source validation
- Memory Safety Issues: Enhanced secure memory cleanup
- Added DTLS protection constants and validation methods
- Improved memory cleanup with secureWipe and zero-filling
- Integrated DTLS protection in handleSecureAnswer
2025-08-24 16:30:06 -04:00
lockbitchat f7940d25e9 COMPLETE Security Overhaul: File Transfer Module
- Eliminated unsafe global state (window.*)
- Added DoS protection with MessageSizeValidator
- Added race condition prevention with AtomicOperations
- Added rate limiting (10 files/minute)
- Added secure memory management with SecureMemoryManager
- Wrapped handleFileChunk in atomic operations
- Updated all methods with security enhancements
2025-08-24 16:04:49 -04:00
lockbitchat f368defcf1 Update iOS Splash Screens 2025-08-24 15:22:53 -04:00
lockbitchat 7fdf7a1b89 Fix non-working PWA modal buttons
- Fixed 'Got it' and 'Later' buttons in iOS Safari modal
- Fixed 'Close' button in fallback instructions modal
- Fixed 'OK' button in final dismissal notification
- Replaced broken inline onclick with proper event listeners
- Added comprehensive button event handling with logging

Bug: Modal buttons were unresponsive due to inline onclick issues
2025-08-23 17:48:46 -04:00
lockbitchat a28aacbe6e Fix PWA mobile UX and modal button handlers
- Fixed iOS Safari modal buttons not working (removed inline onclick)
- Added 10-second auto-popup for install instructions
- Removed floating install buttons on mobile devices
- Enhanced mobile experience with modal-only approach
- Added proper event listeners for all modal buttons
- Improved cross-platform PWA installation flow

Mobile UX: Modal-only, Desktop: Button + Modal
2025-08-23 17:40:32 -04:00
lockbitchat 959e137171 Add 10-second auto PWA install prompt
- New users now see install prompt automatically after 10 seconds
- Smart cancellation prevents duplicate prompts
- Added PWAUtils methods for delayed prompt management
- Enhanced user onboarding experience
- Respects installation status and user preferences
2025-08-23 17:30:12 -04:00
lockbitchat 434301fe6f 🐛 Fix PWA install prompt showing after installation
Fixed critical bug where PWA install message continued showing after app installation

-  Enhanced PWA installation status detection
-  Fixed install prompt logic to hide after installation
-  Improved Service Worker update handling
-  Added proper installation state management
-  Enhanced iOS Safari PWA detection
-  Added installation preferences storage

- Added installationChecked flag for better state management
- Enhanced checkInstallationStatus() method with multiple detection methods
- Improved shouldShowPrompt() logic to prevent showing after installation
- Added periodic installation monitoring for iOS devices
- Enhanced Service Worker activation event handling
- Added PWAUtils.checkInstallationStatus() utility method

- public/src/pwa/install-prompt.js (major refactor)
- public/index.html (PWA logic improvements)
- public/sw.js (Service Worker enhancements)

- PWA install message no longer shows after successful installation
- Only update notifications are shown for installed PWAs
- Proper distinction between install prompts and update notifications

Version: Enhanced Security Edition v4.01.413
2025-08-23 17:21:32 -04:00
lockbitchat 235e3e06cb fix № v4.01.412 2025-08-21 18:02:25 -04:00
lockbitchat 81b0e4bd3e Translation of the download button into English 2025-08-21 17:45:43 -04:00
lockbitchat cef5654cf8 Update to pre-release version. Removed logging that was used for debugging. Added typing of files that the user sends with size restrictions. 2025-08-21 17:40:17 -04:00
lockbitchat 3eab0588db updated documentation for v4.1.223 with new security features
- Added comprehensive connection security overhaul with advanced mutex framework (15s timeout)
- Added race condition protection via atomic key generation and serialized operations
- Added multi-stage validation pipeline with automatic rollback and precise error phase detection
- Added enhanced MITM protections (key fingerprints, session anti-hijacking, mutual challenges, package integrity)
- Added secure key storage system with WeakMap isolation, private storage, validation, rotation, emergency wipe, and monitoring
- Added production-ready security logging with environment detection, data sanitization, log level control, rate limiting, and memory cleanup

- Updated `public/README.md`: “What’s New” for v4.01.223, expanded to 15-layer security (added layers 13–15)
- Updated `public/SECURITY.md`: added new architecture items; updated supported versions (4.1.x = MILITARY-GRADE, 15 layers); new upgrade recommendation
- Updated `public/doc/SECURITY-ARCHITECTURE.md`: 12→15 layers, added layers 13–15, revised performance (total ~78.5ms), updated dates/versions
- Updated `public/doc/CRYPTOGRAPHY.md`: added `SecureKeyManager`, `SecureLogger`, `ConnectionMutexManager`; updated TOC; updated dates/versions
- Updated `public/doc/API.md`: added Security Framework APIs and usage examples; added TOC
- Created `public/SECURITY_UPDATES_v4.1.md`: comprehensive v4.1.223 security update summary
2025-08-21 15:45:07 -04:00
lockbitchat 430ff2a6af - Removed redundant console.log statements to clean up the code
- Added English comments for better readability and GitHub compatibility
2025-08-21 05:16:41 -04:00
lockbitchat 31485989f7 feat(security): comprehensive connection security overhaul with mutex framework
Implemented robust security framework with custom withMutex system:

**Race condition protection:**
- Custom _withMutex('connectionOperation') implementation with 15s timeout
- Atomic key generation through _generateEncryptionKeys()
- Serialized connection operations to prevent conflicts

**Multi-stage validation pipeline:**
- Step-by-step validation (keys, fingerprints, SDP)
- Automatic rollback via _cleanupFailedOfferCreation() on failures
- Error phase detection for precise diagnostics

**Enhanced MITM protection:**
- Unique encryption key fingerprints
- Session ID anti-hijacking protection
- Mutual authentication challenge system
- Package integrity validation

**Advanced logging & monitoring:**
- Secure logging without sensitive data leaks
- Operation tracking via unique operationId
- Comprehensive error diagnostics and phase tracking
- Deadlock detection with emergency recovery

Breaking changes: Connection establishment now requires mutex coordination
2025-08-21 04:07:16 -04:00
lockbitchat 9b2884a3af implemented secure key storage with WeakMap isolation
- Replaced public key properties (`encryptionKey`, `macKey`, etc.) with private `WeakMap`-based storage
- Added secure access methods: `_initializeSecureKeyStorage()`, `_getSecureKey()`, `_setSecureKey()`
- Implemented validation (`_validateKeyValue()`), rotation (`_rotateKeys()`), and emergency wipe (`_emergencyKeyWipe()`)
- Updated `cleanupConnection()` and `disconnect()` for secure key handling
- Added getters/setters for backward compatibility with existing code
- Integrated key security monitoring via `_startKeySecurityMonitoring()`
- Ensured keys are inaccessible via direct property access or debugger

Fixes: Direct exposure of sensitive keys in memory
Security: Prevents unauthorized access, enforces key lifetime limits
2025-08-21 00:06:28 -04:00
lockbitchat 7ee5ec6208 Security: Implement secure logging to prevent data leaks in console
- Add production mode detection and secure logging system
- Replace console.log with _secureLog() that sanitizes sensitive data
- Hide encryption keys, message content, and security details from logs
- Implement log level control (production: warn+error only, dev: debug+)
- Add data sanitization for keys, tokens, buffers, and sensitive strings
- Prevent log spam with automatic rate limiting and cleanup
- Maintain useful debugging info while protecting user privacy
- Add automatic memory cleanup for log counters

Security Impact: HIGH - Prevents sensitive data exposure through browser console
Changes:
- Production: Only critical errors/warnings logged
- Development: Safe debugging info without sensitive content
- All message content, encryption keys, and tokens are now sanitized
- Automatic detection of production vs development environment
2025-08-20 23:34:56 -04:00
lockbitchat 2fb7e47e05 Merge branch 'main' of https://github.com/SecureBitChat/securebit-chat 2025-08-20 23:04:43 -04:00
lockbitchat 73850f423c Security: Fix critical global exposure vulnerability in WebRTC Manager
- Remove dangerous global window exposure of encryption keys and manager instance
- Replace window.webrtcManager/globalWebRTCManager with secure limited API
- Add window.secureBitChat with validated public methods only
- Implement automatic monitoring for unauthorized global exposure attempts
- Add security integrity checks and emergency shutdown capability
- Prevent access to sensitive data (encryption keys, session data, internal methods)
- Maintain full functionality while securing internal implementation

BREAKING CHANGE: window.webrtcManager is no longer available
MIGRATION: Use window.secureBitChat.sendMessage() instead of window.webrtcManager.sendMessage()

Security Impact: CRITICAL - Prevents XSS attacks from accessing encryption keys
Tested: All functionality preserved, file transfers working, connections stable
2025-08-20 23:04:29 -04:00
SecureBitChatandGitHub 34289d7b9c Update README.md
Update v4.01.222
2025-08-20 19:17:27 -04:00
145 changed files with 86747 additions and 17647 deletions
+32
View File
@@ -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."
+29
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
# Dependencies
node_modules/
# Local environment noise
.npm/
npm-debug.log*
+199
View File
@@ -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
+32
View File
@@ -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.
+81 -377
View File
@@ -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.
![SecureBit.chat Logo](logo/favicon.ico)
## Security model
**The world's first P2P messenger with Lightning Network payments and military-grade cryptography**
SecureBit.chat uses:
[![Latest Release](https://img.shields.io/github/v/release/SecureBitChat/securebit-chat?style=for-the-badge&logo=github&color=orange)](https://github.com/SecureBitChat/securebit-chat/releases/latest)
[![Live Demo](https://img.shields.io/badge/🌐_Live_Demo-Try_Now-success?style=for-the-badge)](https://securebitchat.github.io/securebit-chat/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT)
[![Security: Military-Grade](https://img.shields.io/badge/Security-Military_Grade-red.svg?style=for-the-badge)]()
- 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** | 🏆 Paypersession | ⚠️ Donations dependent | ✅ Onetime purchase | ⚠️ Donations dependent |
| **Metadata Protection** | 🏆 Full encryption | ⚠️ Sealed Sender (partial) | ⚠️ Minimal metadata | ✅ Onion routing |
| **Key Security** | 🏆 Nonextractable + 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 militarygrade encryption
### Option 2: SelfHost
### 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 WebLNcompatible 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 80056A — ECDH Key Agreement
* NIST SP 800186 — 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 ✅
* 12layer militarygrade 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
* Crossdevice synchronization
### v5.0 (Q4 2025) — QuantumResistant Edition
* CRYSTALSKyber postquantum key exchange
* SPHINCS+ postquantum signatures
* Hybrid classical + postquantum schemes
* Quantumsafe 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
* DHTbased peer discovery
* Builtin 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 clientside
# 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
* 🔄 Postquantum 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
* Outofband verification — Prevents maninthemiddle 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
* Communitydriven — 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 securityfirst 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).
+54 -208
View File
@@ -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.
+16 -197
View File
@@ -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
+20
View File
@@ -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)
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
+36
View File
@@ -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');
}
+1
View File
@@ -0,0 +1 @@
File diff suppressed because one or more lines are too long
+83
View File
@@ -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
+17306
View File
File diff suppressed because it is too large Load Diff
+7
View File
File diff suppressed because one or more lines are too long
Vendored
+3393
View File
File diff suppressed because it is too large Load Diff
+7
View File
File diff suppressed because one or more lines are too long
+36581
View File
File diff suppressed because it is too large Load Diff
+7
View File
File diff suppressed because one or more lines are too long
+27 -845
View File
@@ -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
+63
View File
@@ -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.
+31 -1213
View File
File diff suppressed because it is too large Load Diff
+15 -1349
View File
File diff suppressed because it is too large Load Diff
+42 -709
View File
@@ -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
+86 -3999
View File
File diff suppressed because it is too large Load Diff
+4
View File
File diff suppressed because one or more lines are too long
+267
View File
@@ -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"});
})();
+31
View File
@@ -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"});
})();
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+2 -2
View File
@@ -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",
+10
View File
@@ -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"
}
+2850
View File
File diff suppressed because it is too large Load Diff
+40
View File
@@ -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"
}
}
+11
View File
@@ -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"
}
+203
View File
@@ -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
};
+94
View File
@@ -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();
+3910
View File
File diff suppressed because it is too large Load Diff
+492
View File
@@ -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');
+1
View File
@@ -0,0 +1 @@
+290
View File
@@ -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;
}
+86
View File
@@ -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;
+306
View File
@@ -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;
+60 -61
View File
@@ -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)
)
]);
};
+139 -33
View File
@@ -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;
+200 -153
View File
@@ -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

Some files were not shown because too many files have changed in this diff Show More