39 Commits

Author SHA1 Message Date
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
30 changed files with 2853 additions and 4616 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
+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
+187 -532
View File
@@ -1,185 +1,99 @@
# SecureBit.chat v4.3.120 - UX/UI Redesign + Binary QR System
# SecureBit.chat v4.4.18
<div align="center">
![SecureBit.chat Logo](logo/favicon.ico)
**The world's first P2P messenger with ECDH + DTLS + SAS security and military-grade cryptography**
**World's first P2P messenger with ECDH + DTLS + SAS security and military-grade cryptography**
[![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: ECDH+DTLS+SAS](https://img.shields.io/badge/Security-ECDH%2BDTLS%2BSAS-red.svg?style=for-the-badge)]()
[🚀 Try Now](https://securebitchat.github.io/securebit-chat/) • [📖 Documentation](#-quick-start) • [🔒 Security](#-security) • [🤝 Contribute](#-contributing)
</div>
---
## ✨ What's New in v4.3.120 - UX/UI Redesign + Binary QR System
## 🎯 Overview
### 🎨 Complete UX/UI Redesign
- Fully refreshed interface and improved navigation.
- More intuitive design and consistent experience across platforms.
- Enhanced readability and visual clarity.
SecureBit.chat is a revolutionary peer-to-peer messenger that prioritizes your privacy with military-grade encryption. No servers, no registration, no data collection - just pure, secure communication.
### 📷 Advanced QR Exchange System
- Rebuilt QR connection system for faster and more secure pairing.
- Optimized scanning and decoding speed.
- Improved compatibility with mobile cameras and desktop screens.
### Key Features
### 🗜️ Compressed Connection Codes
- New **binary compression layer** for all connection data.
- Dramatically reduces code length and exchange time.
- Perfect for offline pairing and air-gapped communication.
### 🔄 Binary Connection Protocol
- Replaced old JSON exchange with a **binary data protocol**.
- Boosts performance and reduces handshake latency.
- Enables seamless cross-platform P2P connection setup.
### 🛡️ Revolutionary ECDH + DTLS + SAS Security System
* **Complete PAKE removal** - Eliminated libsodium dependency and PAKE-based authentication
* **ECDH key exchange** - Elliptic Curve Diffie-Hellman for secure key establishment
* **DTLS fingerprint verification** - Transport layer security validation using WebRTC certificates
* **SAS (Short Authentication String)** - 7-digit verification code for MITM attack prevention
* **Single code generation** - SAS generated once on Offer side and shared with Answer side
* **Mutual verification** - Both users must confirm the same SAS code to establish connection
* **Enhanced MITM protection** - Multi-layer defense against man-in-the-middle attacks
* **Real-time verification** - Immediate feedback on connection security status
### 🔒 ASN.1 Full Structure Validation (BREAKING CHANGE)
* **Complete ASN.1 DER parser** for comprehensive key structure verification
* **OID validation** for algorithms and curves (P-256/P-384 only)
* **EC point format verification** (uncompressed format 0x04)
* **SPKI structure validation** with element count and type checking
* **Key size limits** (50-2000 bytes) to prevent DoS attacks
* **BIT STRING validation** ensuring unused bits are 0
* **Fallback support** from P-384 to P-256 for compatibility
* **High-risk vulnerability fix** where keys with valid headers but modified data could be accepted
### 🔐 Enhanced Key Security
* **Full structural validation** according to PKCS standards
* **Complete rewrite** of `validateKeyStructure()` method
* **Enhanced validation** for all key import/export operations
* **Military-grade key verification** exceeding previous standards
### 🔒 Comprehensive Connection Security Overhaul
* **Advanced mutex framework** with 15-second timeout protection
* **Race condition prevention** through atomic key generation
* **Multi-stage validation pipeline** with automatic rollback
* **Enhanced MITM protection** with unique encryption key fingerprints
* **Session ID anti-hijacking** with mutual authentication challenges
* **Package integrity validation** for all connection operations
### 🔐 Secure Key Storage System
* **WeakMap-based isolation** for all cryptographic keys
* **Private key storage** replacing public key properties
* **Secure access methods** with validation and rotation
* **Emergency key wipe** capabilities for threat response
* **Key security monitoring** with lifetime limits enforcement
* **Backward compatibility** maintained through getters/setters
### 🛡️ Production-Ready Security Logging
* **Environment-aware logging** (production vs development)
* **Data sanitization** preventing sensitive information leaks
* **Rate limiting** and automatic memory cleanup
* **Secure debugging** without exposing encryption keys
* **Privacy protection** while maintaining useful diagnostics
### 📱 Progressive Web App (PWA)
* **Install directly** on mobile and desktop devices
* **Offline mode support** with session persistence
* **Improved performance** through smart caching and service workers
* **Native app experience** without app store requirements
### 📂 Secure File Transfer
* **End-to-end encrypted** file transfers over pure P2P WebRTC channels
* **File chunking** with individual encryption per block
* **Hash validation** for every chunk to prevent tampering or MITM attacks
* **Automatic recovery** for lost packets and interrupted transfers
* **AES-GCM 256-bit + ECDH P-384** encryption for files
* **SHA-384 checksums** for integrity enforcement
### 🔍 Enhanced Security Testing
* **Comprehensive data leakage testing** of chat sessions
* **Verified MITM and replay attack resistance**
* **Enhanced memory cleanup algorithms** for session termination
* **Isolated file streams** separated from chat channels
- 🔐 **19-Layer Military Security** - ECDH + DTLS + SAS verification
- 🌐 **Pure P2P Architecture** - No servers, truly decentralized
- 📱 **Progressive Web App** - Install like a native app
- 📂 **Secure File Transfer** - End-to-end encrypted P2P file sharing
- 🔔 **Smart Notifications** - Browser alerts only when away
- 🎭 **Complete Anonymity** - Zero data collection, no registration
---
## 🚀 Try It Now
## ✨ What's New in v4.4.18
### 🌐 [Live Demo — SecureBit.chat](https://securebitchat.github.io/securebit-chat/)
### 🔔 Secure Browser Notifications
- Smart delivery when user is away from chat tab
- Cross-browser compatibility (Chrome, Firefox, Safari, Edge)
- Page Visibility API integration with proper tab focus detection
- XSS protection with text sanitization and URL validation
- Rate limiting and spam protection
- Automatic cleanup and memory management
*No installation required — works directly in your browser with military-grade encryption.*
### 🧹 Code Cleanup & Architecture
- Removed session management logic for simplified architecture
- Eliminated experimental Bluetooth module
- Cleaned debug logging from production code
- Removed test functions from production build
- Enhanced error handling for production stability
**New:** Install as PWA for native app experience on mobile and desktop!
### 🛡️ Security Enhancements
- **ECDH + DTLS + SAS System** - Triple-layer security verification
- **ASN.1 Full Structure Validation** - Complete key structure verification
- **Enhanced MITM Protection** - Multi-layer defense system
- **Secure Key Storage** - WeakMap-based isolation
- **Production-Ready Logging** - Data sanitization and privacy protection
---
## What Makes SecureBit.chat Unique
## 🏆 Why SecureBit.chat?
### 🏆 Industry Leader
### Security Comparison
* **Dominates in 11/15 security categories** vs Signal, Threema, Session
* **First messenger** with enhanced ECDH + DTLS + SAS security
* **Military-grade cryptography** exceeding government standards
* **Zero servers** — truly decentralized P2P architecture
* **PWA technology** — install like native apps without app stores
| Feature | **SecureBit.chat** | Signal | Threema | Session |
|---------|-------------------|--------|---------|---------|
| Architecture | 🏆 Pure P2P WebRTC | ❌ Centralized | ❌ Centralized | ⚠️ Onion network |
| File Transfer | 🏆 P2P encrypted | ✅ Via servers | ✅ Via servers | ✅ Via servers |
| PWA Support | 🏆 Full PWA | ❌ None | ❌ None | ❌ None |
| Registration | 🏆 Anonymous | ❌ Phone required | ✅ ID generated | ✅ Random ID |
| Traffic Obfuscation | 🏆 Advanced | ❌ None | ❌ None | ✅ Onion routing |
| Data Storage | 🏆 Zero storage | ⚠️ Local database | ⚠️ Local + backup | ⚠️ Local database |
| ASN.1 Validation | 🏆 Complete | ⚠️ Basic | ⚠️ Basic | ⚠️ Basic |
**Legend:** 🏆 Category Leader • ✅ Excellent • ⚠️ Partial/Limited • ❌ Not Available
### 🔐 15-Layer Military Security
### 19-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
13. **Mutex framework** — Race condition protection
14. **Secure key storage** WeakMap isolation
15. **Production logging** — Data sanitization
16. **ASN.1 validation** — Complete key structure verification
17. **OID validation** — Algorithm and curve verification
18. **EC point validation** — Format and structure verification
### 🎭 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
* **Secure file transfers** — encrypted P2P file sharing
---
## 🛡️ Security Comparison
| Feature | **SecureBit.chat** | Signal | Threema | Session |
| --------------------------- | ----------------------------- | ---------------------------- | --------------------- | ---------------------- |
| **Architecture** | 🏆 Pure P2P WebRTC | ❌ Centralized servers | ❌ Centralized servers | ⚠️ Onion network |
| **File Transfer** | 🏆 P2P encrypted + chunked | ✅ Encrypted via servers | ✅ Encrypted via servers | ✅ Encrypted via servers |
| **PWA Support** | 🏆 Full PWA installation | ❌ 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 |
| **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 |
| **ASN.1 Validation** | 🏆 Complete structure verification | ⚠️ Basic validation | ⚠️ Basic validation | ⚠️ Basic validation |
**Legend:** 🏆 Category Leader | ✅ Excellent | ⚠️ Partial/Limited | ❌ Not Available
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 with timestamp validation
6. Automatic key rotation (every 5 min/100 messages)
7. MITM verification with out-of-band codes
8. Traffic obfuscation and pattern masking
9. Complete metadata protection
10. Memory protection with no persistent storage
11. Hardware security with non-extractable keys
12. Session isolation and complete cleanup
13. Mutex framework for race condition protection
14. Secure key storage with WeakMap isolation
15. Production logging with data sanitization
16. ASN.1 complete key structure verification
17. OID validation for algorithms and curves
18. EC point format and structure verification
19. Smart notifications with XSS protection
---
@@ -187,21 +101,21 @@
### Option 1: Use Online (Recommended)
1. **Visit:** [https://securebitchat.github.io/securebit-chat/](https://securebitchat.github.io/securebit-chat/)
2. **Install PWA:** Click "Install" button for native app experience
3. **Choose:** *Create Channel* or *Join Channel*
4. **Complete:** Secure key exchange with verification
5. **Verify:** Security codes and start a secure chat
6. **Communicate:** With militarygrade encryption + secure file transfers
1. Visit [securebitchat.github.io/securebit-chat](https://securebitchat.github.io/securebit-chat/)
2. Install PWA by clicking "Install" button for native app experience
3. Choose "Create Channel" or "Join Channel"
4. Complete secure key exchange with verification
5. Verify security codes and start chatting
6. Communicate with military-grade encryption
### Option 2: SelfHost
### Option 2: Self-Host
```bash
# Clone repository
git clone https://github.com/SecureBitChat/securebit-chat.git
cd securebit-chat
# Serve locally (choose one method)
# Serve locally
python -m http.server 8000 # Python
npx serve . # Node.js
php -S localhost:8000 # PHP
@@ -215,32 +129,15 @@ open http://localhost:8000
## 📂 Secure File Transfer
### Features
- **P2P Direct Transfer** - No servers, direct WebRTC channels
- **Military-Grade Encryption** - AES-GCM 256-bit + ECDH P-384
- **Chunk-Level Security** - Individual encryption per file chunk
- **Hash Validation** - SHA-384 checksums prevent tampering
- **Automatic Recovery** - Retry mechanisms for interruptions
- **Stream Isolation** - Separate channels from chat messages
* **P2P Direct Transfer** — No servers involved, direct WebRTC channels
* **Military-Grade Encryption** — AES-GCM 256-bit + ECDH P-384
* **Chunk-Level Security** — Each file chunk individually encrypted
* **Hash Validation** — SHA-384 checksums prevent tampering
* **Automatic Recovery** — Retry mechanisms for interrupted transfers
* **Stream Isolation** — Separate channels from chat messages
### Supported File Types
* **Documents:** PDF, DOC, TXT, MD
* **Images:** JPG, PNG, GIF, WEBP
* **Archives:** ZIP, RAR, 7Z
* **Media:** MP3, MP4, AVI (size limits apply)
* **General:** Any file type up to size limits
### Security Guarantees
* End-to-end encryption with perfect forward secrecy
* MITM attack prevention through hash validation
* Zero server storage — files transfer directly P2P
* Complete cleanup after transfer completion
---
### Supported Files
Documents (PDF, DOC, TXT), Images (JPG, PNG, GIF), Archives (ZIP, RAR), Media (MP3, MP4), and any file type up to size limits.
---
@@ -249,426 +146,182 @@ open http://localhost:8000
### Cryptographic Stack
```
📂 File Transfer Layer: AES-GCM 256-bit + SHA-384 + Chunking
🔐 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
📱 PWA Layer: Service Workers + Cache API
🔒 ASN.1 Layer: Complete DER parsing and validation
📂 File Transfer: AES-GCM 256-bit + SHA-384 + Chunking
🔐 Application: AES-GCM 256-bit + ECDSA P-384
🔑 Key Exchange: ECDH P-384 (Perfect Forward Secrecy)
🛡️ Transport: WebRTC DTLS 1.2
🌐 Network: P2P WebRTC Data Channels
📱 PWA: Service Workers + Cache API
🔒 Validation: Complete ASN.1 DER parsing
```
### Security Standards
* NIST SP 80056A — ECDH Key Agreement
* NIST SP 800186 — Elliptic Curve Cryptography
* RFC 6090 — Fundamental ECC Algorithms
* RFC 8446 — TLS 1.3 for WebRTC
* RFC 3874 — SHA-384 Hash Algorithm
* RFC 5280 — X.509 Certificate Structure
* RFC 5480 — Elliptic Curve Subject Public Key Information
### Standards Compliance
- NIST SP 800-56A (ECDH Key Agreement)
- NIST SP 800-186 (Elliptic Curve Cryptography)
- RFC 8446 (TLS 1.3 for WebRTC)
- RFC 5280 (X.509 Certificate Structure)
- RFC 5480 (EC Subject Public Key Information)
### Browser Requirements
* Modern browser with WebRTC support (Chrome 60+, Firefox 60+, Safari 12+)
* HTTPS connection (required for WebRTC and PWA)
* JavaScript enabled
* Service Worker support for PWA features
Modern browser with WebRTC support (Chrome 60+, Firefox 60+, Safari 12+), HTTPS connection, JavaScript enabled, Service Worker support for PWA.
---
## 🗺️ Development Roadmap
## 🗺️ Roadmap
**Current:** v4.02.442 — ASN.1 Validation & Enhanced Security Edition
**Current: v4.4.18** - Browser Notifications & Code Cleanup
* Complete ASN.1 DER parser for key structure validation
* Enhanced key security with OID and EC point verification
* Breaking changes for improved security standards
* Full PKCS compliance for all cryptographic operations
**Next Releases:**
**Previous:** v4.01.441 — PWA & File Transfer Edition ✅
- **v4.5 (Q2 2025)** - Mobile & Desktop Apps
- Native mobile applications (iOS/Android)
- Electron desktop application
- Push notifications and cross-device sync
* Progressive Web App installation
* Secure P2P file transfer system
* Enhanced security testing and MITM protection
* Improved memory cleanup algorithms
- **v5.0 (Q4 2025)** - Quantum-Resistant Edition
- CRYSTALS-Kyber post-quantum key exchange
- SPHINCS+ post-quantum signatures
- Hybrid classical + post-quantum schemes
**Next Releases**
- **v5.5 (Q2 2026)** - Group Communications
- P2P group chats (up to 8 participants)
- Mesh networking topology
- Anonymous group administration
### v4.5 (Q2 2025) — Mobile & Desktop Apps
* Native mobile applications (iOS/Android)
* Electron desktop application
* Push notifications
* Crossdevice synchronization
* Enhanced PWA features
### 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
* Anonymous group administration
* Group file sharing
### v6.0 (2027) — Decentralized Network
* DHTbased peer discovery
* Builtin onion routing
* Decentralized identity system
* Node incentive mechanisms
- **v6.0 (2027)** - Decentralized Network
- DHT-based peer discovery
- Built-in onion routing
- Decentralized identity system
---
## 🧪 Development
## 💻 Development
### Project Structure
```
securebit-chat/
├── index.html # Main application
├── manifest.json # PWA manifest
├── sw.js # Service worker
├── browserconfig.xml # Browser configuration for PWA
├── index.html # Main application
├── manifest.json # PWA manifest
├── sw.js # Service worker
├── src/
│ ├── components/ui/ # React UI components
│ ├── DownloadApps.js # PWA download/install component
│ │ ── FileTransfer.js # File transfer UI component
│ └── ... # Other UI components
│ ├── crypto/ # Cryptographic utilities
│ └── ASN1Validator.js # Complete ASN.1 DER parser
│ ├── network/ # WebRTC P2P manager
── session/ # Payment session manager
│ ├── transfer/ # File transfer system
│ │ └── EnhancedSecureFileTransfer.js # Secure P2P file transfer
│ ├── pwa/ # PWA management
│ │ ├── install-prompt.js # PWA installation prompts
│ │ ├── offline-manager.js # Offline mode management
│ │ └── pwa-manager.js # PWA lifecycle management
│ └── styles/ # CSS styling
│ ├── pwa.css # PWA-specific styles
│ └── ... # Other stylesheets
├── logo/ # Wallet logos and icons
├── docs/ # Documentation
└── README.md # This file
│ ├── components/ui/ # React UI components
│ ├── crypto/ # Cryptographic utilities
│ │ ── ASN1Validator.js # ASN.1 DER parser
├── network/ # WebRTC P2P manager
│ ├── notifications/ # Browser notifications
├── transfer/ # File transfer system
│ ├── pwa/ # PWA management
── styles/ # CSS styling
├── logo/ # Icons and logos
└── docs/ # Documentation
```
### Technology Stack
* **Frontend:** Pure JavaScript + React (via CDN)
* **PWA:** Service Workers + Cache API + Web App Manifest + Install Prompts
* **Cryptography:** Web Crypto API + custom ECDH/ECDSA + ASN.1 DER parser
* **Network:** WebRTC P2P Data Channels
* **File Transfer:** Enhanced secure P2P streaming with chunked encryption
* **Payments:** Lightning Network via WebLN
* **Offline Support:** Smart caching with offline-manager
* **Styling:** TailwindCSS + custom CSS + PWA-specific styles
### Development Setup
### Build Workflow
```bash
# Clone repository
git clone https://github.com/SecureBitChat/securebit-chat.git
cd securebit-chat
# CSS changes (Tailwind)
npm run build:css
# No build process required — pure clientside
# Just serve the files over HTTPS
# JavaScript/JSX changes
npm run build:js
# For development
python -m http.server 8000
# Full rebuild (recommended)
npm run build
# For production
# Deploy to any static hosting (GitHub Pages, Netlify, etc.)
# Development with live server
npm run dev
```
**Important:** Always rebuild after changes. Source files are in `src/`, generated files in `assets/` and `dist/`. Never edit generated files directly.
### Technology Stack
- **Frontend:** Pure JavaScript + React (via CDN)
- **PWA:** Service Workers + Cache API + Web App Manifest
- **Cryptography:** Web Crypto API + custom ECDH/ECDSA + ASN.1 parser
- **Network:** WebRTC P2P Data Channels
- **Notifications:** Browser Notifications API + Page Visibility API
- **File Transfer:** Enhanced secure P2P streaming with chunked encryption
- **Styling:** TailwindCSS + custom CSS
---
## 🛡️ Security
### Security Audit Status
* ✅ Internal cryptographic review completed
* ✅ P2P protocol security analysis completed
* ✅ File transfer security validation completed
* ✅ MITM and replay attack resistance verified
* ✅ ASN.1 validation and key structure verification completed
* 🔄 Professional security audit planned Q3 2025
* 🔄 Postquantum cryptography review for v5.0
### Audit Status
- ✅ Internal cryptographic review completed
- ✅ P2P protocol security analysis completed
- ✅ File transfer security validation completed
- ✅ ASN.1 validation and key verification completed
- 🔄 Professional security audit planned Q3 2025
### Vulnerability Reporting
Contact: **SecureBitChat@proton.me**
See **SECURITY.md** for detailed security policy and reporting instructions.
Contact: **[SecureBitChat@proton.me](mailto:SecureBitChat@proton.me)**
See **SECURITY.md** for detailed security policy.
### Security Features
* Perfect Forward Secrecy — Past messages and files 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
* File integrity — SHA-384 hash validation prevents tampering
* Chunked encryption — Individual encryption per file block
* **ASN.1 validation** — Complete key structure verification according to PKCS standards
* **OID validation** — Algorithm and curve verification for cryptographic operations
* **EC point validation** — Format and structure verification for elliptic curve keys
- Perfect Forward Secrecy for messages and files
- Out-of-band verification prevents MITM attacks
- Traffic obfuscation defeats network analysis
- Memory protection with no persistent storage
- Complete ASN.1 key structure validation
- File integrity with SHA-384 hash validation
---
## 📊 Performance
### Benchmarks
* Connection setup: < 3 seconds
* Message latency: < 100 ms (P2P direct)
* File transfer speed: Up to 5 MB/s per connection
* Throughput: Up to 1 MB/s per connection
* Memory usage: < 50 MB for active session
* Battery impact: Minimal (optimized WebRTC)
* PWA install size: < 2 MB
* **Key validation time:** < 10 ms (ASN.1 parsing)
### Scalability
* Concurrent connections: Limited by device capabilities
* Message size: Up to 2000 characters
* File size: Up to 100 MB per file
* File types: All formats supported
* 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
- **Connection setup:** < 3 seconds
- **Message latency:** < 100 ms (P2P direct)
- **File transfer speed:** Up to 5 MB/s
- **Memory usage:** < 50 MB active session
- **PWA install size:** < 2 MB
- **Key validation:** < 10 ms (ASN.1 parsing)
---
## 🤝 Contributing
We welcome contributions from the community!
### How to Contribute
We welcome contributions! Here's how:
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
2. Create feature branch: `git checkout -b feature/amazing-feature`
3. Commit changes: `git commit -m "Add amazing feature"`
4. Push to branch: `git push origin feature/amazing-feature`
5. Open Pull Request
### Contribution Areas
* 🔐 Cryptography — Security improvements and audits
* 🌐 Network — P2P optimization and reliability
* 📂 File Transfer — EnhancedSecureFileTransfer improvements
* 📱 PWA — Install prompts, offline management, and PWA lifecycle
* 🎨 UI/UX — Interface improvements, FileTransfer and DownloadApps components
* 📚 Documentation — Guides, tutorials, translations
* **🔒 ASN.1 Validation** — Enhanced key structure verification and parsing
### Development Guidelines
* Follow existing code style
* Add tests for new features
* Update documentation
* Respect securityfirst principles
* Test PWA functionality across devices
* **Validate all cryptographic operations** with enhanced ASN.1 parsing
🔐 Cryptography • 🌐 Network • 🔔 Notifications • 📂 File Transfer • 📱 PWA • 🎨 UI/UX • 📚 Documentation • 🔒 ASN.1 Validation
---
## 📞 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
- **Email:** SecureBitChat@proton.me
- **GitHub:** Issues & Discussions
- **Security:** SecureBitChat@proton.me
---
## ⚠️ 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
* **File transfers are protected with the same military-grade cryptography as chat messages**
* **All cryptographic keys now undergo complete ASN.1 structure validation**
While SecureBit.chat implements military-grade cryptography, no system is 100% secure. Always verify security codes out-of-band and keep devices updated.
### 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
* File sharing and transfer
This software is provided "as is" for educational and research purposes. Users are responsible for compliance with local laws regarding cryptographic software and private communications.
### 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
* **Files are transferred directly P2P with zero server storage**
SecureBit.chat collects zero data, stores nothing, requires no registration, and uses no servers. All data exists only in browser memory with direct P2P connections.
---
## 🎯 Why Choose SecureBit.chat?
## 📄 License
### For Privacy Advocates
MIT License - see **LICENSE** file for details.
* True zero-knowledge architecture
* Military-grade encryption standards
* Complete anonymity and untraceability
* Resistance to censorship and surveillance
* **Secure P2P file sharing without servers**
* **Complete ASN.1 validation for cryptographic keys**
### For Mobile Users
* **Progressive Web App installation**
* **Offline mode support**
* **Native app experience without app stores**
* **Works on all modern mobile devices**
### For Developers
* 100% open source transparency
* Modern cryptographic standards
* Clean, auditable codebase
* Extensible modular architecture
* **PWA best practices implementation**
* **Complete ASN.1 DER parser for key validation**
### For Everyone
* **Install like native apps**
* **Works offline with session persistence**
* Works on all modern devices
* Intuitive user interface
* Professional security standards
* **Secure file transfers included**
* **Enhanced key security with ASN.1 validation**
---
## 🔧 Development Workflow
### Making Changes and Recompiling
When you make changes to the source code, you need to recompile the assets. Here's the proper workflow:
#### 1. **CSS Changes** (Tailwind classes, styles)
```bash
# Rebuild only CSS
npm run build:css
# Or watch for changes during development
npm run watch
```
#### 2. **JavaScript/JSX Changes** (React components, logic)
```bash
# Rebuild only JavaScript
npm run build:js
# Or rebuild everything
npm run build
```
#### 3. **Full Rebuild** (recommended after major changes)
```bash
# Complete rebuild of all assets
npm run build
```
#### 4. **Development with Live Server**
```bash
# Build and start development server
npm run dev
# Or use custom server
npm run serve
```
### File Structure After Build
```
├── assets/
│ ├── tailwind.css # ← Generated from src/styles/tw-input.css
│ ├── fontawesome/ # ← Local Font Awesome assets
│ └── fonts/ # ← Local Google Fonts
├── dist/
│ ├── app.js # ← Generated from src/app.jsx
│ ├── app-boot.js # ← Generated from src/scripts/app-boot.js
│ └── qr-local.js # ← Generated from src/scripts/qr-local.js
└── src/ # ← Source files (edit these)
├── app.jsx
├── scripts/
├── styles/
└── components/
```
### Important Notes
- **Always rebuild after changes** to see them in the browser
- **CSS changes** require `npm run build:css`
- **JS/JSX changes** require `npm run build:js`
- **Source files** are in `src/` directory
- **Generated files** are in `assets/` and `dist/` directories
- **Never edit** files in `assets/` or `dist/` directly
### Troubleshooting Build Issues
#### CSS not updating?
```bash
# Clear cache and rebuild
rm assets/tailwind.css
npm run build:css
```
#### JavaScript errors?
```bash
# Check for syntax errors in source files
npm run build:js
```
#### All changes not showing?
```bash
# Hard refresh browser (Ctrl+F5) or clear browser cache
# Then rebuild everything
npm run build
```
100% open source with full transparency, no telemetry, and zero data collection.
---
@@ -682,6 +335,8 @@ npm run build
---
**Latest Release: v4.02.442** — ASN.1 Validation & Enhanced Security
**Latest Release: v4.4.18** - Browser Notifications & Code Cleanup
</div>
[🚀 Try Now](https://securebitchat.github.io/securebit-chat/) • [⭐ Star on GitHub](https://github.com/SecureBitChat/securebit-chat)
</div>
+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)
+1
View File
@@ -0,0 +1 @@
+1 -1
View File
File diff suppressed because one or more lines are too long
+828 -1004
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
File diff suppressed because one or more lines are too long
Vendored
+279 -353
View File
@@ -267,11 +267,16 @@ var EnhancedConnectionSetup = ({
toggleQrManualMode,
nextQrFrame,
prevQrFrame,
markAnswerCreated
markAnswerCreated,
notificationIntegrationRef,
isGeneratingKeys,
handleCreateOffer
}) => {
const [mode, setMode] = React.useState("select");
const [notificationPermissionRequested, setNotificationPermissionRequested] = React.useState(false);
const resetToSelect = () => {
setMode("select");
setIsGeneratingKeys(false);
onClearData();
};
const handleVerificationConfirm = () => {
@@ -280,6 +285,76 @@ var EnhancedConnectionSetup = ({
const handleVerificationReject = () => {
onVerifyConnection(false);
};
const requestNotificationPermissionOnInteraction = async () => {
if (notificationPermissionRequested) {
return;
}
try {
if (!("Notification" in window)) {
return;
}
if (!window.isSecureContext && window.location.protocol !== "https:" && window.location.hostname !== "localhost") {
return;
}
const currentPermission = typeof Notification !== "undefined" && Notification ? Notification.permission : "denied";
if (currentPermission === "default" && typeof Notification !== "undefined" && Notification) {
const permission = await Notification.requestPermission();
if (permission === "granted") {
try {
if (window.NotificationIntegration && webrtcManagerRef.current) {
const integration = new window.NotificationIntegration(webrtcManagerRef.current);
await integration.init();
notificationIntegrationRef.current = integration;
}
} catch (error) {
}
setTimeout(() => {
try {
const welcomeNotification = new Notification("SecureBit Chat", {
body: "Notifications enabled! You will receive alerts for new messages.",
icon: "/logo/icon-192x192.png",
tag: "welcome-notification"
});
welcomeNotification.onclick = () => {
welcomeNotification.close();
};
setTimeout(() => {
welcomeNotification.close();
}, 5e3);
} catch (error) {
}
}, 1e3);
}
} else if (currentPermission === "granted") {
try {
if (window.NotificationIntegration && webrtcManagerRef.current && !notificationIntegrationRef.current) {
const integration = new window.NotificationIntegration(webrtcManagerRef.current);
await integration.init();
notificationIntegrationRef.current = integration;
}
} catch (error) {
}
setTimeout(() => {
try {
const testNotification = new Notification("SecureBit Chat", {
body: "Notifications are working! You will receive alerts for new messages.",
icon: "/logo/icon-192x192.png",
tag: "test-notification"
});
testNotification.onclick = () => {
testNotification.close();
};
setTimeout(() => {
testNotification.close();
}, 5e3);
} catch (error) {
}
}, 1e3);
}
setNotificationPermissionRequested(true);
} catch (error) {
}
};
if (showVerification) {
return React.createElement("div", {
className: "min-h-[calc(100vh-104px)] flex items-center justify-center p-4"
@@ -327,7 +402,15 @@ var EnhancedConnectionSetup = ({
// Create Connection
React.createElement("div", {
key: "create",
onClick: () => setMode("create"),
onClick: () => {
requestNotificationPermissionOnInteraction();
setMode("create");
setTimeout(() => {
if (webrtcManagerRef.current) {
handleCreateOffer();
}
}, 100);
},
className: "card-minimal rounded-xl p-6 cursor-pointer group flex-1 create"
}, [
React.createElement("div", {
@@ -399,7 +482,10 @@ var EnhancedConnectionSetup = ({
// Join Connection
React.createElement("div", {
key: "join",
onClick: () => setMode("join"),
onClick: () => {
requestNotificationPermissionOnInteraction();
setMode("join");
},
className: "card-minimal rounded-xl p-6 cursor-pointer group flex-1 join"
}, [
React.createElement("div", {
@@ -510,16 +596,15 @@ var EnhancedConnectionSetup = ({
key: "description",
className: "text-secondary text-sm mb-4"
}, "Creating cryptographically strong keys and codes to protect against attacks"),
!showOfferStep && React.createElement("button", {
key: "create-btn",
onClick: onCreateOffer,
disabled: connectionStatus === "connecting",
className: `w-full btn-primary text-white py-3 px-4 rounded-lg font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed`
!showOfferStep && isGeneratingKeys && React.createElement("div", {
key: "loading-state",
className: "w-full py-3 px-4 rounded-lg font-medium transition-all duration-200 bg-blue-500/10 border border-blue-500/20 text-blue-400 flex items-center justify-center"
}, [
React.createElement("i", {
className: "fas fa-shield-alt mr-2"
key: "spinner",
className: "fas fa-spinner fa-spin mr-2"
}),
"Create secure keys"
"Generating secure keys..."
]),
showOfferStep && React.createElement("div", {
key: "offer-result",
@@ -622,58 +707,6 @@ var EnhancedConnectionSetup = ({
])
])
]),
// Step 2 - Session Type Selection
// showOfferStep && React.createElement('div', {
// key: 'step2',
// className: "card-minimal rounded-xl p-6"
// }, [
// React.createElement('div', {
// key: 'step-header',
// className: "flex items-center mb-4"
// }, [
// React.createElement('div', {
// key: 'number',
// className: "w-8 h-8 bg-green-500 text-white rounded-lg flex items-center justify-center font-semibold text-sm mr-3"
// }, '2'),
// React.createElement('h3', {
// key: 'title',
// className: "text-lg font-medium text-primary"
// }, "Select session type")
// ]),
// React.createElement('p', {
// key: 'description',
// className: "text-secondary text-sm mb-4"
// }, "Choose a session plan or use limited demo mode for testing."),
// React.createElement(SessionTypeSelector, {
// key: 'session-selector',
// onSelectType: (sessionType) => {
// // Save the selected session type
// setSelectedSessionType(sessionType);
// console.log('🎯 Session type selected:', sessionType);
// // FIX: For demo sessions, we immediately call automatic activation
// if (sessionType === 'demo') {
// console.log('🎮 Demo session selected, scheduling automatic activation...');
// // Delay activation for 2 seconds to stabilize
// setTimeout(() => {
// if (sessionManager) {
// console.log('🚀 Triggering demo session activation from selection...');
// handleDemoVerification();
// }
// }, 2000);
// }
// // Open a modal payment window
// if (typeof window.showPaymentModal === 'function') {
// window.showPaymentModal(sessionType);
// } else {
// // Fallback - show session information
// console.log('Selected session type:', sessionType);
// }
// },
// onCancel: resetToSelect,
// sessionManager: window.sessionManager
// })
// ]),
// Step 3 - Waiting for response
showOfferStep && React.createElement("div", {
key: "step2",
className: "card-minimal rounded-xl p-6"
@@ -710,24 +743,13 @@ var EnhancedConnectionSetup = ({
}),
"Scan QR Code"
])
// React.createElement('button', {
// key: 'bluetooth-btn',
// onClick: () => { try { document.dispatchEvent(new CustomEvent('open-bluetooth-transfer', { detail: { role: 'responder' } })); } catch {} },
// className: "px-4 py-2 bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 border border-blue-500/20 rounded text-sm font-medium transition-all duration-200"
// }, [
// React.createElement('i', {
// key: 'icon',
// className: 'fas fa-bluetooth mr-2'
// }),
// 'Bluetooth'
// ])
]),
React.createElement("textarea", {
key: "input",
value: answerInput,
onChange: (e2) => {
setAnswerInput(e2.target.value);
if (e2.target.value.trim().length > 0) {
onChange: (e) => {
setAnswerInput(e.target.value);
if (e.target.value.trim().length > 0) {
if (typeof markAnswerCreated === "function") {
markAnswerCreated();
}
@@ -803,9 +825,9 @@ var EnhancedConnectionSetup = ({
React.createElement("textarea", {
key: "input",
value: offerInput,
onChange: (e2) => {
setOfferInput(e2.target.value);
if (e2.target.value.trim().length > 0) {
onChange: (e) => {
setOfferInput(e.target.value);
if (e.target.value.trim().length > 0) {
if (typeof markAnswerCreated === "function") {
markAnswerCreated();
}
@@ -830,17 +852,6 @@ var EnhancedConnectionSetup = ({
}),
"Scan QR Code"
]),
// React.createElement('button', {
// key: 'bluetooth-btn',
// onClick: () => { try { document.dispatchEvent(new CustomEvent('open-bluetooth-transfer', { detail: { role: 'initiator' } })); } catch {} },
// className: "px-4 py-2 bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 border border-blue-500/20 rounded text-sm font-medium transition-all duration-200"
// }, [
// React.createElement('i', {
// key: 'icon',
// className: 'fas fa-bluetooth mr-2'
// }),
// 'Bluetooth'
// ]),
React.createElement("button", {
key: "process-btn",
onClick: onCreateAnswer,
@@ -1107,9 +1118,9 @@ var EnhancedChatInterface = ({
setShowScrollButton(false);
}
};
const handleKeyPress = (e2) => {
if (e2.key === "Enter" && !e2.shiftKey) {
e2.preventDefault();
const handleKeyPress = (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
onSendMessage();
}
};
@@ -1294,7 +1305,7 @@ var EnhancedChatInterface = ({
[
React.createElement("textarea", {
value: messageInput,
onChange: (e2) => setMessageInput(e2.target.value),
onChange: (e) => setMessageInput(e.target.value),
onKeyDown: handleKeyPress,
placeholder: "Enter message to encrypt...",
rows: 2,
@@ -1354,23 +1365,10 @@ var EnhancedSecureP2PChat = () => {
const [qrCodeUrl, setQrCodeUrl] = React.useState("");
const [showQRScanner, setShowQRScanner] = React.useState(false);
const [showQRScannerModal, setShowQRScannerModal] = React.useState(false);
const [showBluetoothTransfer, setShowBluetoothTransfer] = React.useState(false);
const [bluetoothAutoRole, setBluetoothAutoRole] = React.useState(null);
React.useEffect(() => {
const openBt = (e2) => {
try {
const role = e2?.detail?.role || null;
setBluetoothAutoRole(role);
setShowBluetoothTransfer(true);
} catch {
}
};
document.addEventListener("open-bluetooth-transfer", openBt);
return () => document.removeEventListener("open-bluetooth-transfer", openBt);
}, []);
const [bluetoothManager, setBluetoothManager] = React.useState(null);
const [isGeneratingKeys, setIsGeneratingKeys2] = React.useState(false);
const [isVerified, setIsVerified] = React.useState(false);
const [securityLevel, setSecurityLevel] = React.useState(null);
const [sessionTimeLeft, setSessionTimeLeft] = React.useState(0);
const [localVerificationConfirmed, setLocalVerificationConfirmed] = React.useState(false);
const [remoteVerificationConfirmed, setRemoteVerificationConfirmed] = React.useState(false);
const [bothVerificationsConfirmed, setBothVerificationsConfirmed] = React.useState(false);
@@ -1395,7 +1393,7 @@ var EnhancedSecureP2PChat = () => {
const now = Date.now();
const answerAge = now - (connectionState.answerCreatedAt || 0);
const maxPreserveTime = 3e5;
const hasAnswerData = answerData && answerData.trim().length > 0 || answerInput && answerInput.trim().length > 0;
const hasAnswerData = answerData && typeof answerData === "string" && answerData.trim().length > 0 || answerInput && answerInput.trim().length > 0;
const hasAnswerQR = qrCodeUrl && qrCodeUrl.trim().length > 0;
const shouldPreserve = connectionState.hasActiveAnswer && answerAge < maxPreserveTime && !connectionState.isUserInitiatedDisconnect || hasAnswerData && answerAge < maxPreserveTime && !connectionState.isUserInitiatedDisconnect || hasAnswerQR && answerAge < maxPreserveTime && !connectionState.isUserInitiatedDisconnect;
return shouldPreserve;
@@ -1409,8 +1407,8 @@ var EnhancedSecureP2PChat = () => {
React.useEffect(() => {
window.forceCleanup = () => {
handleClearData();
if (webrtcManagerRef.current) {
webrtcManagerRef.current.disconnect();
if (webrtcManagerRef2.current) {
webrtcManagerRef2.current.disconnect();
}
};
window.clearLogs = () => {
@@ -1423,8 +1421,9 @@ var EnhancedSecureP2PChat = () => {
delete window.clearLogs;
};
}, []);
const webrtcManagerRef = React.useRef(null);
window.webrtcManagerRef = webrtcManagerRef;
const webrtcManagerRef2 = React.useRef(null);
const notificationIntegrationRef = React.useRef(null);
window.webrtcManagerRef = webrtcManagerRef2;
const addMessageWithAutoScroll = React.useCallback((message, type) => {
const newMessage = {
message,
@@ -1465,7 +1464,7 @@ var EnhancedSecureP2PChat = () => {
}
window.isUpdatingSecurity = true;
try {
if (webrtcManagerRef.current) {
if (webrtcManagerRef2.current) {
setSecurityLevel({
level: "MAXIMUM",
score: 100,
@@ -1476,7 +1475,7 @@ var EnhancedSecureP2PChat = () => {
isRealData: true
});
if (window.DEBUG_MODE) {
const currentLevel = webrtcManagerRef.current.ecdhKeyPair && webrtcManagerRef.current.ecdsaKeyPair ? await webrtcManagerRef.current.calculateSecurityLevel() : {
const currentLevel = webrtcManagerRef2.current.ecdhKeyPair && webrtcManagerRef2.current.ecdsaKeyPair ? await webrtcManagerRef2.current.calculateSecurityLevel() : {
level: "MAXIMUM",
score: 100,
sessionType: "premium",
@@ -1509,7 +1508,7 @@ var EnhancedSecureP2PChat = () => {
}
}, [messages]);
React.useEffect(() => {
if (webrtcManagerRef.current) {
if (webrtcManagerRef2.current) {
console.log("\u26A0\uFE0F WebRTC Manager already initialized, skipping...");
return;
}
@@ -1584,8 +1583,8 @@ var EnhancedSecureP2PChat = () => {
setLocalVerificationConfirmed(false);
setRemoteVerificationConfirmed(false);
setBothVerificationsConfirmed(false);
setOfferData(null);
setAnswerData(null);
setOfferData("");
setAnswerData("");
setOfferInput("");
setAnswerInput("");
setShowOfferStep(false);
@@ -1596,8 +1595,8 @@ var EnhancedSecureP2PChat = () => {
setTimeout(() => {
setConnectionStatus("disconnected");
setShowVerification(false);
setOfferData(null);
setAnswerData(null);
setOfferData("");
setAnswerData("");
setOfferInput("");
setAnswerInput("");
setShowOfferStep(false);
@@ -1617,8 +1616,8 @@ var EnhancedSecureP2PChat = () => {
setLocalVerificationConfirmed(false);
setRemoteVerificationConfirmed(false);
setBothVerificationsConfirmed(false);
setOfferData(null);
setAnswerData(null);
setOfferData("");
setAnswerData("");
setOfferInput("");
setAnswerInput("");
setShowOfferStep(false);
@@ -1671,7 +1670,7 @@ var EnhancedSecureP2PChat = () => {
if (typeof console.clear === "function") {
console.clear();
}
webrtcManagerRef.current = new EnhancedSecureWebRTCManager(
webrtcManagerRef2.current = new EnhancedSecureWebRTCManager(
handleMessage,
handleStatusChange,
handleKeyExchange,
@@ -1679,12 +1678,22 @@ var EnhancedSecureP2PChat = () => {
handleAnswerError,
handleVerificationStateChange
);
handleMessage(" SecureBit.chat Enhanced Security Edition v4.3.120 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.", "system");
if (typeof Notification !== "undefined" && Notification && Notification.permission === "granted" && window.NotificationIntegration && !notificationIntegrationRef.current) {
try {
const integration = new window.NotificationIntegration(webrtcManagerRef2.current);
integration.init().then(() => {
notificationIntegrationRef.current = integration;
}).catch((error) => {
});
} catch (error) {
}
}
handleMessage(" SecureBit.chat Enhanced Security Edition v4.4.18 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.", "system");
const handleBeforeUnload = (event) => {
if (event.type === "beforeunload" && !isTabSwitching) {
if (webrtcManagerRef.current && webrtcManagerRef.current.isConnected()) {
if (webrtcManagerRef2.current && webrtcManagerRef2.current.isConnected()) {
try {
webrtcManagerRef.current.sendSystemMessage({
webrtcManagerRef2.current.sendSystemMessage({
type: "peer_disconnect",
reason: "user_disconnect",
timestamp: Date.now()
@@ -1692,12 +1701,12 @@ var EnhancedSecureP2PChat = () => {
} catch (error) {
}
setTimeout(() => {
if (webrtcManagerRef.current) {
webrtcManagerRef.current.disconnect();
if (webrtcManagerRef2.current) {
webrtcManagerRef2.current.disconnect();
}
}, 100);
} else if (webrtcManagerRef.current) {
webrtcManagerRef.current.disconnect();
} else if (webrtcManagerRef2.current) {
webrtcManagerRef2.current.disconnect();
}
} else if (isTabSwitching) {
event.preventDefault();
@@ -1725,8 +1734,8 @@ var EnhancedSecureP2PChat = () => {
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
if (webrtcManagerRef.current) {
webrtcManagerRef.current.setFileTransferCallbacks(
if (webrtcManagerRef2.current) {
webrtcManagerRef2.current.setFileTransferCallbacks(
// Progress callback
(progress) => {
console.log("File progress:", progress);
@@ -1749,9 +1758,9 @@ var EnhancedSecureP2PChat = () => {
a.download = fileData.fileName;
a.click();
setTimeout(() => fileData.revokeObjectURL(url), 15e3);
} catch (e2) {
console.error("Download failed:", e2);
addMessageWithAutoScroll(` File upload error: ${String(e2?.message || e2)}`, "system");
} catch (e) {
console.error("Download failed:", e);
addMessageWithAutoScroll(` File upload error: ${String(e?.message || e)}`, "system");
}
}
}, "Download")
@@ -1778,9 +1787,9 @@ var EnhancedSecureP2PChat = () => {
clearTimeout(tabSwitchTimeout);
tabSwitchTimeout = null;
}
if (webrtcManagerRef.current) {
webrtcManagerRef.current.disconnect();
webrtcManagerRef.current = null;
if (webrtcManagerRef2.current) {
webrtcManagerRef2.current.disconnect();
webrtcManagerRef2.current = null;
}
};
}, []);
@@ -1867,8 +1876,8 @@ var EnhancedSecureP2PChat = () => {
const QR_SIZE = isDesktop ? 720 : 512;
const url = await (window.generateQRCode ? window.generateQRCode(current, { errorCorrectionLevel: "M", margin: 2, size: QR_SIZE }) : Promise.resolve(""));
if (url) setQrCodeUrl(url);
} catch (e2) {
console.warn("Animated QR render error (current):", e2);
} catch (e) {
console.warn("Animated QR render error (current):", e);
}
setQrFrameIndex((qrAnimationRef.current?.idx || 0) % (qrAnimationRef.current?.chunks?.length || 1) + 1);
};
@@ -1974,8 +1983,8 @@ var EnhancedSecureP2PChat = () => {
setQrFrameIndex(1);
return;
}
} catch (e2) {
console.warn("Binary QR generation failed, falling back to compressed:", e2?.message || e2);
} catch (e) {
console.warn("Binary QR generation failed, falling back to compressed:", e?.message || e);
}
}
if (typeof window.generateCompressedQRCode === "function") {
@@ -1998,8 +2007,8 @@ var EnhancedSecureP2PChat = () => {
setQrFrameIndex(1);
return;
}
} catch (e2) {
console.warn("Compressed QR generation failed, falling back to plain:", e2?.message || e2);
} catch (e) {
console.warn("Compressed QR generation failed, falling back to plain:", e?.message || e);
}
}
const payload = typeof data === "string" ? data : JSON.stringify(data);
@@ -2193,8 +2202,8 @@ var EnhancedSecureP2PChat = () => {
qrChunksBufferRef.current = { id: null, total: 0, seen: /* @__PURE__ */ new Set(), items: [] };
setShowQRScannerModal(false);
return Promise.resolve(true);
} catch (e2) {
console.warn("Binary chunks reconstruction failed:", e2);
} catch (e) {
console.warn("Binary chunks reconstruction failed:", e);
return Promise.resolve(false);
}
}
@@ -2252,8 +2261,8 @@ var EnhancedSecureP2PChat = () => {
qrChunksBufferRef.current = { id: null, total: 0, seen: /* @__PURE__ */ new Set(), items: [] };
setShowQRScannerModal(false);
return Promise.resolve(true);
} catch (e2) {
console.warn("Binary chunks reconstruction failed:", e2);
} catch (e) {
console.warn("Binary chunks reconstruction failed:", e);
return Promise.resolve(false);
}
}
@@ -2312,8 +2321,8 @@ var EnhancedSecureP2PChat = () => {
qrChunksBufferRef.current = { id: null, total: 0, seen: /* @__PURE__ */ new Set(), items: [] };
setShowQRScannerModal(false);
return Promise.resolve(true);
} catch (e2) {
console.warn("RAW multi-frame reconstruction failed:", e2);
} catch (e) {
console.warn("RAW multi-frame reconstruction failed:", e);
return Promise.resolve(false);
}
} else if (hdr.rt === "bin") {
@@ -2340,8 +2349,8 @@ var EnhancedSecureP2PChat = () => {
qrChunksBufferRef.current = { id: null, total: 0, seen: /* @__PURE__ */ new Set(), items: [] };
setShowQRScannerModal(false);
return Promise.resolve(true);
} catch (e2) {
console.warn("BIN multi-frame reconstruction failed:", e2);
} catch (e) {
console.warn("BIN multi-frame reconstruction failed:", e);
return Promise.resolve(false);
}
} else if (window.receiveAndProcess) {
@@ -2363,8 +2372,8 @@ var EnhancedSecureP2PChat = () => {
setShowQRScannerModal(false);
return Promise.resolve(true);
}
} catch (e2) {
console.warn("COSE multi-chunk processing failed:", e2);
} catch (e) {
console.warn("COSE multi-chunk processing failed:", e);
}
return Promise.resolve(false);
} else {
@@ -2444,135 +2453,14 @@ var EnhancedSecureP2PChat = () => {
return true;
}
};
const handleBluetoothKeyReceived = async (keyData, deviceId) => {
try {
console.log("Bluetooth key received from device:", deviceId);
const keyString = JSON.stringify(keyData, null, 2);
if (showOfferStep) {
setAnswerInput(keyString);
} else {
setOfferInput(keyString);
}
setMessages((prev) => [...prev, {
message: "\u{1F535} Bluetooth key received successfully!",
type: "success"
}]);
setShowBluetoothTransfer(false);
} catch (error) {
console.error("Failed to process Bluetooth key:", error);
setMessages((prev) => [...prev, {
message: "Failed to process Bluetooth key: " + error.message,
type: "error"
}]);
}
};
const handleBluetoothStatusChange = (statusType, data) => {
console.log("Bluetooth status change:", statusType, data);
switch (statusType) {
case "bluetooth_ready":
setMessages((prev) => [...prev, {
message: "\u{1F535} Bluetooth ready for key exchange",
type: "info"
}]);
break;
case "connected":
setMessages((prev) => [...prev, {
message: `\u{1F535} Connected to device: ${data.deviceName}`,
type: "success"
}]);
break;
case "key_sent":
setMessages((prev) => [...prev, {
message: "\u{1F535} Public key sent via Bluetooth",
type: "success"
}]);
break;
case "key_received":
setMessages((prev) => [...prev, {
message: "\u{1F535} Public key received via Bluetooth",
type: "success"
}]);
break;
case "auto_connection_starting":
setMessages((prev) => [...prev, {
message: "\u{1F535} Starting automatic connection...",
type: "info"
}]);
break;
case "creating_offer":
setMessages((prev) => [...prev, {
message: "\u{1F535} Creating secure offer...",
type: "info"
}]);
break;
case "offer_sent":
setMessages((prev) => [...prev, {
message: "\u{1F535} Offer sent, waiting for answer...",
type: "info"
}]);
break;
case "waiting_for_answer":
setMessages((prev) => [...prev, {
message: "\u{1F535} Waiting for answer...",
type: "info"
}]);
break;
case "processing_answer":
setMessages((prev) => [...prev, {
message: "\u{1F535} Processing answer...",
type: "info"
}]);
break;
case "waiting_for_verification":
setMessages((prev) => [...prev, {
message: "\u{1F535} Waiting for verification...",
type: "info"
}]);
break;
case "auto_connection_complete":
setMessages((prev) => [...prev, {
message: "\u{1F535} Automatic connection completed!",
type: "success"
}]);
break;
case "auto_connection_failed":
setMessages((prev) => [...prev, {
message: "\u{1F535} Automatic connection failed: " + (data.error || "Unknown error"),
type: "error"
}]);
break;
}
};
const handleBluetoothError = (error) => {
console.error("Bluetooth error:", error);
setMessages((prev) => [...prev, {
message: "Bluetooth error: " + error.message,
type: "error"
}]);
};
const handleBluetoothAutoConnection = async (connectionData) => {
try {
console.log("Bluetooth auto connection completed:", connectionData);
setMessages((prev) => [...prev, {
message: "\u{1F535} Bluetooth auto connection completed successfully!",
type: "success"
}]);
setShowBluetoothTransfer(false);
} catch (error) {
console.error("Failed to process auto connection:", error);
setMessages((prev) => [...prev, {
message: "Failed to process auto connection: " + error.message,
type: "error"
}]);
}
};
const handleCreateOffer = async () => {
try {
setIsGeneratingKeys2(true);
setOfferData("");
setShowOfferStep(false);
setShowQRCode(false);
setQrCodeUrl("");
const offer = await webrtcManagerRef.current.createSecureOffer();
const offer = await webrtcManagerRef2.current.createSecureOffer();
setOfferData(offer);
setShowOfferStep(true);
const offerString = typeof offer === "object" ? JSON.stringify(offer) : offer;
@@ -2626,8 +2514,8 @@ var EnhancedSecureP2PChat = () => {
} catch {
}
}
} catch (e2) {
console.warn("Offer QR generation failed:", e2);
} catch (e) {
console.warn("Offer QR generation failed:", e);
}
const existingMessages = messages.filter(
(m) => m.type === "system" && (m.message.includes("Secure invitation created") || m.message.includes("Send the encrypted code"))
@@ -2656,6 +2544,8 @@ var EnhancedSecureP2PChat = () => {
id: Date.now(),
timestamp: Date.now()
}]);
} finally {
setIsGeneratingKeys2(false);
}
};
const handleCreateAnswer = async () => {
@@ -2678,14 +2568,24 @@ var EnhancedSecureP2PChat = () => {
}]);
let offer;
try {
console.log("DEBUG: Processing offer input:", offerInput.trim().substring(0, 100) + "...");
console.log("DEBUG: decodeAnyPayload available:", typeof window.decodeAnyPayload === "function");
console.log("DEBUG: decompressIfNeeded available:", typeof window.decompressIfNeeded === "function");
if (typeof window.decodeAnyPayload === "function") {
console.log("DEBUG: Using decodeAnyPayload...");
const any = window.decodeAnyPayload(offerInput.trim());
console.log("DEBUG: decodeAnyPayload result type:", typeof any);
console.log("DEBUG: decodeAnyPayload result:", any);
offer = typeof any === "string" ? JSON.parse(any) : any;
} else {
console.log("DEBUG: Using decompressIfNeeded...");
const rawText = typeof window.decompressIfNeeded === "function" ? window.decompressIfNeeded(offerInput.trim()) : offerInput.trim();
console.log("DEBUG: decompressIfNeeded result:", rawText.substring(0, 100) + "...");
offer = JSON.parse(rawText);
}
console.log("DEBUG: Final offer:", offer);
} catch (parseError) {
console.error("DEBUG: Parse error:", parseError);
throw new Error(`Invalid invitation format: ${parseError.message}`);
}
if (!offer || typeof offer !== "object") {
@@ -2695,7 +2595,7 @@ var EnhancedSecureP2PChat = () => {
if (!isValidOfferType) {
throw new Error("Invalid invitation type. Expected offer or enhanced_secure_offer");
}
const answer = await webrtcManagerRef.current.createSecureAnswer(offer);
const answer = await webrtcManagerRef2.current.createSecureAnswer(offer);
setAnswerData(answer);
setShowAnswerStep(true);
const answerString = typeof answer === "object" ? JSON.stringify(answer) : answer;
@@ -2749,10 +2649,10 @@ var EnhancedSecureP2PChat = () => {
} catch {
}
}
} catch (e2) {
console.warn("Answer QR generation failed:", e2);
} catch (e) {
console.warn("Answer QR generation failed:", e);
}
if (e.target.value.trim().length > 0) {
if (answerInput.trim().length > 0) {
if (typeof markAnswerCreated === "function") {
markAnswerCreated();
}
@@ -2833,7 +2733,7 @@ var EnhancedSecureP2PChat = () => {
if (!answerType || answerType !== "answer" && answerType !== "enhanced_secure_answer") {
throw new Error("Invalid response type. Expected answer or enhanced_secure_answer");
}
await webrtcManagerRef.current.handleSecureAnswer(answer);
await webrtcManagerRef2.current.handleSecureAnswer(answer);
if (pendingSession) {
setPendingSession(null);
setMessages((prev) => [...prev, {
@@ -2919,10 +2819,37 @@ var EnhancedSecureP2PChat = () => {
setConnectionStatus("failed");
}
};
const handleVerifyConnection = (isValid) => {
const handleVerifyConnection = async (isValid) => {
if (isValid) {
webrtcManagerRef.current.confirmVerification();
webrtcManagerRef2.current.confirmVerification();
setLocalVerificationConfirmed(true);
try {
if (window.NotificationIntegration && webrtcManagerRef2.current && !notificationIntegrationRef.current) {
const integration = new window.NotificationIntegration(webrtcManagerRef2.current);
await integration.init();
notificationIntegrationRef.current = integration;
const status = integration.getStatus();
if (status.permission === "granted") {
setMessages((prev) => [...prev, {
message: "\u2713 Notifications enabled - you will receive alerts when the tab is inactive",
type: "system",
id: Date.now(),
timestamp: Date.now()
}]);
} else {
setMessages((prev) => [...prev, {
message: "\u2139 Notifications disabled - you can enable them using the button on the main page",
type: "system",
id: Date.now(),
timestamp: Date.now()
}]);
}
} else if (notificationIntegrationRef.current) {
} else {
}
} catch (error) {
console.warn("Failed to initialize notifications:", error);
}
} else {
setMessages((prev) => [...prev, {
message: " Verification rejected. The connection is unsafe! Session reset..",
@@ -2936,8 +2863,8 @@ var EnhancedSecureP2PChat = () => {
setShowVerification(false);
setVerificationCode("");
setConnectionStatus("disconnected");
setOfferData(null);
setAnswerData(null);
setOfferData("");
setAnswerData("");
setOfferInput("");
setAnswerInput("");
setShowOfferStep(false);
@@ -2956,15 +2883,15 @@ var EnhancedSecureP2PChat = () => {
if (!messageInput.trim()) {
return;
}
if (!webrtcManagerRef.current) {
if (!webrtcManagerRef2.current) {
return;
}
if (!webrtcManagerRef.current.isConnected()) {
if (!webrtcManagerRef2.current.isConnected()) {
return;
}
try {
addMessageWithAutoScroll(messageInput.trim(), "sent");
await webrtcManagerRef.current.sendMessage(messageInput);
await webrtcManagerRef2.current.sendMessage(messageInput);
setMessageInput("");
} catch (error) {
const msg = String(error?.message || error);
@@ -2979,6 +2906,7 @@ var EnhancedSecureP2PChat = () => {
setOfferInput("");
setAnswerInput("");
setShowOfferStep(false);
setIsGeneratingKeys2(false);
if (!shouldPreserveAnswerData()) {
setShowAnswerStep(false);
}
@@ -3008,53 +2936,59 @@ var EnhancedSecureP2PChat = () => {
document.dispatchEvent(new CustomEvent("peer-disconnect"));
};
const handleDisconnect = () => {
setSessionTimeLeft(0);
updateConnectionState({
status: "disconnected",
isUserInitiatedDisconnect: true
});
if (webrtcManagerRef.current) {
webrtcManagerRef.current.disconnect();
}
setKeyFingerprint("");
setVerificationCode("");
setSecurityLevel(null);
setIsVerified(false);
setShowVerification(false);
setConnectionStatus("disconnected");
setLocalVerificationConfirmed(false);
setRemoteVerificationConfirmed(false);
setBothVerificationsConfirmed(false);
setConnectionStatus("disconnected");
setShowVerification(false);
setOfferData(null);
setAnswerData(null);
setOfferInput("");
setAnswerInput("");
setShowOfferStep(false);
setShowAnswerStep(false);
setKeyFingerprint("");
setVerificationCode("");
setSecurityLevel(null);
setIsVerified(false);
setMessages([]);
if (typeof console.clear === "function") {
console.clear();
}
document.dispatchEvent(new CustomEvent("peer-disconnect"));
document.dispatchEvent(new CustomEvent("disconnected"));
document.dispatchEvent(new CustomEvent("session-cleanup", {
detail: {
timestamp: Date.now(),
reason: "manual_disconnect"
}
}));
setTimeout(() => {
try {
setSessionTimeLeft(0);
}, 500);
handleClearData();
setTimeout(() => {
}, 1e3);
updateConnectionState({
status: "disconnected",
isUserInitiatedDisconnect: true
});
if (webrtcManagerRef2.current) {
webrtcManagerRef2.current.disconnect();
}
if (notificationIntegrationRef.current) {
notificationIntegrationRef.current.cleanup();
notificationIntegrationRef.current = null;
}
setKeyFingerprint("");
setVerificationCode("");
setSecurityLevel(null);
setIsVerified(false);
setShowVerification(false);
setConnectionStatus("disconnected");
setLocalVerificationConfirmed(false);
setRemoteVerificationConfirmed(false);
setBothVerificationsConfirmed(false);
setOfferData("");
setAnswerData("");
setOfferInput("");
setAnswerInput("");
setShowOfferStep(false);
setShowAnswerStep(false);
setIsGeneratingKeys2(false);
setShowQRCode(false);
setQrCodeUrl("");
setShowQRScanner(false);
setShowQRScannerModal(false);
setMessages([]);
if (typeof console.clear === "function") {
console.clear();
}
document.dispatchEvent(new CustomEvent("peer-disconnect"));
document.dispatchEvent(new CustomEvent("disconnected"));
document.dispatchEvent(new CustomEvent("session-cleanup", {
detail: {
timestamp: Date.now(),
reason: "manual_disconnect"
}
}));
handleClearData();
setTimeout(() => {
setSessionTimeLeft(0);
}, 500);
console.log("Disconnect completed successfully");
} catch (error) {
console.error("Error during disconnect:", error);
}
};
const handleSessionActivated = (session) => {
let message;
@@ -3173,7 +3107,7 @@ var EnhancedSecureP2PChat = () => {
isConnected: isConnectedAndVerified,
securityLevel,
// sessionManager removed - all features enabled by default
webrtcManager: webrtcManagerRef.current
webrtcManager: webrtcManagerRef2.current
}),
React.createElement(
"main",
@@ -3193,7 +3127,7 @@ var EnhancedSecureP2PChat = () => {
isVerified,
chatMessagesRef,
scrollToBottom,
webrtcManager: webrtcManagerRef.current
webrtcManager: webrtcManagerRef2.current
});
})() : React.createElement(EnhancedConnectionSetup, {
onCreateOffer: handleCreateOffer,
@@ -3230,7 +3164,10 @@ var EnhancedSecureP2PChat = () => {
nextQrFrame,
prevQrFrame,
// PAKE passwords removed - using SAS verification instead
markAnswerCreated
markAnswerCreated,
notificationIntegrationRef,
isGeneratingKeys,
handleCreateOffer
})
),
// QR Scanner Modal
@@ -3306,18 +3243,7 @@ var EnhancedSecureP2PChat = () => {
])
])
])
]),
// Bluetooth Key Transfer Modal
showBluetoothTransfer && window.BluetoothKeyTransfer && React.createElement(window.BluetoothKeyTransfer, {
key: "bluetooth-transfer-modal",
webrtcManager: webrtcManagerRef.current,
onKeyReceived: handleBluetoothKeyReceived,
onStatusChange: handleBluetoothStatusChange,
onError: handleBluetoothError,
onAutoConnection: handleBluetoothAutoConnection,
isVisible: showBluetoothTransfer,
onClose: () => setShowBluetoothTransfer(false)
})
])
]);
};
function initializeApp() {
+3 -3
View File
File diff suppressed because one or more lines are too long
+10 -9
View File
@@ -5,15 +5,17 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' ;
script-src 'self';
style-src 'self';
font-src 'self' ;
connect-src 'self' https: ;
img-src 'self' data:;
manifest-src 'self';
worker-src 'self';
font-src 'self';
connect-src 'self' https: wss: ws:;
img-src 'self' data: https:;
media-src 'none';
object-src 'none';
frame-src 'none';
frame-ancestors 'none';
worker-src 'self';
manifest-src 'self';
form-action 'self';
upgrade-insecure-requests;
report-uri /csp-report;
@@ -110,12 +112,11 @@
<meta name="theme-color" content="#ff6b35">
<meta name="msapplication-navbutton-color" content="#ff6b35">
<!-- Security Headers for PWA -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' ; style-src 'self' ; font-src 'self' ; connect-src 'self' https: wss: ws:; img-src 'self' data: https:; media-src 'none'; object-src 'none'; frame-src 'none'; worker-src 'self';">
<!-- Security Headers for PWA - CSP is already defined above -->
<!-- GitHub Pages SEO -->
<meta name="description" content="SecureBit.chat v4.3.120 — P2P messenger with ECDH + DTLS + SAS security and 18-layer military-grade cryptography">
<meta name="description" content="SecureBit.chat v4.4.18 — P2P messenger with ECDH + DTLS + SAS security and 18-layer military-grade cryptography">
<meta name="keywords" content="P2P messenger, ECDH, DTLS, SAS, encryption, WebRTC, privacy, ASN.1 validation, military-grade security, 18-layer defense, MITM protection, PFS">
<meta name="author" content="Volodymyr">
<link rel="canonical" href="https://github.com/SecureBitChat/securebit-chat/">
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "SecureBit.chat v4.3.120 - ECDH + DTLS + SAS",
"name": "SecureBit.chat v4.4.18 - ECDH + DTLS + SAS",
"short_name": "SecureBit",
"description": "P2P messenger with ECDH + DTLS + SAS security, military-grade cryptography and Lightning Network payments",
"start_url": "./",
+251 -289
View File
@@ -280,12 +280,17 @@
toggleQrManualMode,
nextQrFrame,
prevQrFrame,
markAnswerCreated
markAnswerCreated,
notificationIntegrationRef,
isGeneratingKeys,
handleCreateOffer
}) => {
const [mode, setMode] = React.useState('select');
const [notificationPermissionRequested, setNotificationPermissionRequested] = React.useState(false);
const resetToSelect = () => {
setMode('select');
setIsGeneratingKeys(false);
onClearData();
};
@@ -296,6 +301,112 @@
const handleVerificationReject = () => {
onVerifyConnection(false);
};
// Request notification permission on first user interaction
const requestNotificationPermissionOnInteraction = async () => {
if (notificationPermissionRequested) {
return; // Already requested
}
try {
// Check if Notification API is supported
if (!('Notification' in window)) {
return;
}
// Check if we're in a secure context
if (!window.isSecureContext && window.location.protocol !== 'https:' && window.location.hostname !== 'localhost') {
return;
}
// Check current permission status
const currentPermission = (typeof Notification !== 'undefined' && Notification)
? Notification.permission
: 'denied';
// Only request if permission is default (not granted or denied)
if (currentPermission === 'default' && typeof Notification !== 'undefined' && Notification) {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
// Initialize notification integration immediately
try {
if (window.NotificationIntegration && webrtcManagerRef.current) {
const integration = new window.NotificationIntegration(webrtcManagerRef.current);
await integration.init();
// Store reference for cleanup
notificationIntegrationRef.current = integration;
}
} catch (error) {
// Handle error silently
}
// Send welcome notification
setTimeout(() => {
try {
const welcomeNotification = new Notification('SecureBit Chat', {
body: 'Notifications enabled! You will receive alerts for new messages.',
icon: '/logo/icon-192x192.png',
tag: 'welcome-notification'
});
welcomeNotification.onclick = () => {
welcomeNotification.close();
};
setTimeout(() => {
welcomeNotification.close();
}, 5000);
} catch (error) {
// Handle error silently
}
}, 1000);
}
} else if (currentPermission === 'granted') {
// Initialize notification integration immediately
try {
if (window.NotificationIntegration && webrtcManagerRef.current && !notificationIntegrationRef.current) {
const integration = new window.NotificationIntegration(webrtcManagerRef.current);
await integration.init();
// Store reference for cleanup
notificationIntegrationRef.current = integration;
}
} catch (error) {
// Handle error silently
}
// Test notification to confirm it works
setTimeout(() => {
try {
const testNotification = new Notification('SecureBit Chat', {
body: 'Notifications are working! You will receive alerts for new messages.',
icon: '/logo/icon-192x192.png',
tag: 'test-notification'
});
testNotification.onclick = () => {
testNotification.close();
};
setTimeout(() => {
testNotification.close();
}, 5000);
} catch (error) {
// Handle error silently
}
}, 1000);
}
setNotificationPermissionRequested(true);
} catch (error) {
// Handle error silently
}
};
if (showVerification) {
return React.createElement('div', {
@@ -346,7 +457,16 @@
// Create Connection
React.createElement('div', {
key: 'create',
onClick: () => setMode('create'),
onClick: () => {
requestNotificationPermissionOnInteraction();
setMode('create');
// Автоматически запускаем генерацию ключей
setTimeout(() => {
if (webrtcManagerRef.current) {
handleCreateOffer();
}
}, 100);
},
className: "card-minimal rounded-xl p-6 cursor-pointer group flex-1 create"
}, [
React.createElement('div', {
@@ -418,7 +538,10 @@
// Join Connection
React.createElement('div', {
key: 'join',
onClick: () => setMode('join'),
onClick: () => {
requestNotificationPermissionOnInteraction();
setMode('join');
},
className: "card-minimal rounded-xl p-6 cursor-pointer group flex-1 join"
}, [
React.createElement('div', {
@@ -515,7 +638,7 @@
className: "text-xl font-semibold text-primary mb-2"
}, 'Creating a secure channel')
]),
// Step 1
!showAnswerStep && React.createElement('div', {
key: 'step1',
@@ -538,16 +661,15 @@
key: 'description',
className: "text-secondary text-sm mb-4"
}, "Creating cryptographically strong keys and codes to protect against attacks"),
!showOfferStep && React.createElement('button', {
key: 'create-btn',
onClick: onCreateOffer,
disabled: connectionStatus === 'connecting',
className: `w-full btn-primary text-white py-3 px-4 rounded-lg font-medium transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed`
!showOfferStep && isGeneratingKeys && React.createElement('div', {
key: 'loading-state',
className: "w-full py-3 px-4 rounded-lg font-medium transition-all duration-200 bg-blue-500/10 border border-blue-500/20 text-blue-400 flex items-center justify-center"
}, [
React.createElement('i', {
className: 'fas fa-shield-alt mr-2'
key: 'spinner',
className: 'fas fa-spinner fa-spin mr-2'
}),
'Create secure keys'
'Generating secure keys...'
]),
showOfferStep && React.createElement('div', {
@@ -591,7 +713,7 @@
} catch { return typeof offerData === 'object' ? JSON.stringify(offerData) : (offerData || ''); }
})(),
className: "flex-1 px-3 py-2 bg-orange-500/10 hover:bg-orange-500/20 text-orange-400 border border-orange-500/20 rounded text-sm font-medium"
}, 'Copy invitation code')
}, 'Copy invitation code'),
]),
showQRCode && qrCodeUrl && React.createElement('div', {
key: 'qr-container',
@@ -653,63 +775,8 @@
])
])
])
]),
]),
// Step 2 - Session Type Selection
// showOfferStep && React.createElement('div', {
// key: 'step2',
// className: "card-minimal rounded-xl p-6"
// }, [
// React.createElement('div', {
// key: 'step-header',
// className: "flex items-center mb-4"
// }, [
// React.createElement('div', {
// key: 'number',
// className: "w-8 h-8 bg-green-500 text-white rounded-lg flex items-center justify-center font-semibold text-sm mr-3"
// }, '2'),
// React.createElement('h3', {
// key: 'title',
// className: "text-lg font-medium text-primary"
// }, "Select session type")
// ]),
// React.createElement('p', {
// key: 'description',
// className: "text-secondary text-sm mb-4"
// }, "Choose a session plan or use limited demo mode for testing."),
// React.createElement(SessionTypeSelector, {
// key: 'session-selector',
// onSelectType: (sessionType) => {
// // Save the selected session type
// setSelectedSessionType(sessionType);
// console.log('🎯 Session type selected:', sessionType);
// // FIX: For demo sessions, we immediately call automatic activation
// if (sessionType === 'demo') {
// console.log('🎮 Demo session selected, scheduling automatic activation...');
// // Delay activation for 2 seconds to stabilize
// setTimeout(() => {
// if (sessionManager) {
// console.log('🚀 Triggering demo session activation from selection...');
// handleDemoVerification();
// }
// }, 2000);
// }
// // Open a modal payment window
// if (typeof window.showPaymentModal === 'function') {
// window.showPaymentModal(sessionType);
// } else {
// // Fallback - show session information
// console.log('Selected session type:', sessionType);
// }
// },
// onCancel: resetToSelect,
// sessionManager: window.sessionManager
// })
// ]),
// Step 3 - Waiting for response
showOfferStep && React.createElement('div', {
key: 'step2',
className: "card-minimal rounded-xl p-6"
@@ -746,17 +813,6 @@
}),
'Scan QR Code'
]),
// React.createElement('button', {
// key: 'bluetooth-btn',
// onClick: () => { try { document.dispatchEvent(new CustomEvent('open-bluetooth-transfer', { detail: { role: 'responder' } })); } catch {} },
// className: "px-4 py-2 bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 border border-blue-500/20 rounded text-sm font-medium transition-all duration-200"
// }, [
// React.createElement('i', {
// key: 'icon',
// className: 'fas fa-bluetooth mr-2'
// }),
// 'Bluetooth'
// ])
]),
React.createElement('textarea', {
key: 'input',
@@ -870,17 +926,6 @@
}),
'Scan QR Code'
]),
// React.createElement('button', {
// key: 'bluetooth-btn',
// onClick: () => { try { document.dispatchEvent(new CustomEvent('open-bluetooth-transfer', { detail: { role: 'initiator' } })); } catch {} },
// className: "px-4 py-2 bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 border border-blue-500/20 rounded text-sm font-medium transition-all duration-200"
// }, [
// React.createElement('i', {
// key: 'icon',
// className: 'fas fa-bluetooth mr-2'
// }),
// 'Bluetooth'
// ]),
React.createElement('button', {
key: 'process-btn',
onClick: onCreateAnswer,
@@ -1100,6 +1145,7 @@
};
};
const EnhancedChatInterface = ({
messages,
messageInput,
@@ -1432,25 +1478,12 @@
const [qrCodeUrl, setQrCodeUrl] = React.useState('');
const [showQRScanner, setShowQRScanner] = React.useState(false);
const [showQRScannerModal, setShowQRScannerModal] = React.useState(false);
const [isGeneratingKeys, setIsGeneratingKeys] = React.useState(false);
// Bluetooth key transfer states
const [showBluetoothTransfer, setShowBluetoothTransfer] = React.useState(false);
const [bluetoothAutoRole, setBluetoothAutoRole] = React.useState(null);
React.useEffect(() => {
const openBt = (e) => {
try {
const role = e?.detail?.role || null;
setBluetoothAutoRole(role);
setShowBluetoothTransfer(true);
} catch {}
};
document.addEventListener('open-bluetooth-transfer', openBt);
return () => document.removeEventListener('open-bluetooth-transfer', openBt);
}, []);
const [bluetoothManager, setBluetoothManager] = React.useState(null);
const [isVerified, setIsVerified] = React.useState(false);
const [securityLevel, setSecurityLevel] = React.useState(null);
const [sessionTimeLeft, setSessionTimeLeft] = React.useState(0);
// Mutual verification states
const [localVerificationConfirmed, setLocalVerificationConfirmed] = React.useState(false);
@@ -1497,7 +1530,7 @@
const maxPreserveTime = 300000;
const hasAnswerData = (answerData && answerData.trim().length > 0) ||
const hasAnswerData = (answerData && typeof answerData === 'string' && answerData.trim().length > 0) ||
(answerInput && answerInput.trim().length > 0);
const hasAnswerQR = qrCodeUrl && qrCodeUrl.trim().length > 0;
@@ -1544,6 +1577,7 @@
}, []);
const webrtcManagerRef = React.useRef(null);
const notificationIntegrationRef = React.useRef(null);
// Expose for modules/UI that run outside this closure (e.g., inline handlers)
// Safe because it's a ref object and we maintain it centrally here
window.webrtcManagerRef = webrtcManagerRef;
@@ -1751,8 +1785,8 @@
setBothVerificationsConfirmed(false);
// Clear connection data
setOfferData(null);
setAnswerData(null);
setOfferData('');
setAnswerData('');
setOfferInput('');
setAnswerInput('');
setShowOfferStep(false);
@@ -1767,8 +1801,8 @@
setConnectionStatus('disconnected');
setShowVerification(false);
setOfferData(null);
setAnswerData(null);
setOfferData('');
setAnswerData('');
setOfferInput('');
setAnswerInput('');
setShowOfferStep(false);
@@ -1796,8 +1830,8 @@
setBothVerificationsConfirmed(false);
// Clear connection data
setOfferData(null);
setAnswerData(null);
setOfferData('');
setAnswerData('');
setOfferInput('');
setAnswerInput('');
setShowOfferStep(false);
@@ -1876,7 +1910,21 @@
handleVerificationStateChange
);
handleMessage(' SecureBit.chat Enhanced Security Edition v4.3.120 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.', 'system');
// Initialize notification integration if permission was already granted
if (typeof Notification !== 'undefined' && Notification && Notification.permission === 'granted' && window.NotificationIntegration && !notificationIntegrationRef.current) {
try {
const integration = new window.NotificationIntegration(webrtcManagerRef.current);
integration.init().then(() => {
notificationIntegrationRef.current = integration;
}).catch((error) => {
// Handle error silently
});
} catch (error) {
// Handle error silently
}
}
handleMessage(' SecureBit.chat Enhanced Security Edition v4.4.18 - ECDH + DTLS + SAS initialized. Ready to establish a secure connection with ECDH key exchange, DTLS fingerprint verification, and SAS authentication to prevent MITM attacks.', 'system');
const handleBeforeUnload = (event) => {
if (event.type === 'beforeunload' && !isTabSwitching) {
@@ -2753,155 +2801,13 @@
}
};
// Bluetooth key transfer handlers
const handleBluetoothKeyReceived = async (keyData, deviceId) => {
try {
console.log('Bluetooth key received from device:', deviceId);
// Convert key data to the format expected by the app
const keyString = JSON.stringify(keyData, null, 2);
// Determine which input to populate based on current mode
if (showOfferStep) {
// In "Waiting for peer's response" mode - populate answerInput
setAnswerInput(keyString);
} else {
// In "Paste secure invitation" mode - populate offerInput
setOfferInput(keyString);
}
setMessages(prev => [...prev, {
message: '🔵 Bluetooth key received successfully!',
type: 'success'
}]);
// Close Bluetooth transfer modal
setShowBluetoothTransfer(false);
} catch (error) {
console.error('Failed to process Bluetooth key:', error);
setMessages(prev => [...prev, {
message: 'Failed to process Bluetooth key: ' + error.message,
type: 'error'
}]);
}
};
const handleBluetoothStatusChange = (statusType, data) => {
console.log('Bluetooth status change:', statusType, data);
switch (statusType) {
case 'bluetooth_ready':
setMessages(prev => [...prev, {
message: '🔵 Bluetooth ready for key exchange',
type: 'info'
}]);
break;
case 'connected':
setMessages(prev => [...prev, {
message: `🔵 Connected to device: ${data.deviceName}`,
type: 'success'
}]);
break;
case 'key_sent':
setMessages(prev => [...prev, {
message: '🔵 Public key sent via Bluetooth',
type: 'success'
}]);
break;
case 'key_received':
setMessages(prev => [...prev, {
message: '🔵 Public key received via Bluetooth',
type: 'success'
}]);
break;
case 'auto_connection_starting':
setMessages(prev => [...prev, {
message: '🔵 Starting automatic connection...',
type: 'info'
}]);
break;
case 'creating_offer':
setMessages(prev => [...prev, {
message: '🔵 Creating secure offer...',
type: 'info'
}]);
break;
case 'offer_sent':
setMessages(prev => [...prev, {
message: '🔵 Offer sent, waiting for answer...',
type: 'info'
}]);
break;
case 'waiting_for_answer':
setMessages(prev => [...prev, {
message: '🔵 Waiting for answer...',
type: 'info'
}]);
break;
case 'processing_answer':
setMessages(prev => [...prev, {
message: '🔵 Processing answer...',
type: 'info'
}]);
break;
case 'waiting_for_verification':
setMessages(prev => [...prev, {
message: '🔵 Waiting for verification...',
type: 'info'
}]);
break;
case 'auto_connection_complete':
setMessages(prev => [...prev, {
message: '🔵 Automatic connection completed!',
type: 'success'
}]);
break;
case 'auto_connection_failed':
setMessages(prev => [...prev, {
message: '🔵 Automatic connection failed: ' + (data.error || 'Unknown error'),
type: 'error'
}]);
break;
}
};
const handleBluetoothError = (error) => {
console.error('Bluetooth error:', error);
setMessages(prev => [...prev, {
message: 'Bluetooth error: ' + error.message,
type: 'error'
}]);
};
const handleBluetoothAutoConnection = async (connectionData) => {
try {
console.log('Bluetooth auto connection completed:', connectionData);
setMessages(prev => [...prev, {
message: '🔵 Bluetooth auto connection completed successfully!',
type: 'success'
}]);
// Close Bluetooth transfer modal
setShowBluetoothTransfer(false);
// The connection is now established automatically
// No need for manual offer/answer processing
} catch (error) {
console.error('Failed to process auto connection:', error);
setMessages(prev => [...prev, {
message: 'Failed to process auto connection: ' + error.message,
type: 'error'
}]);
}
};
const handleCreateOffer = async () => {
try {
// All security features are enabled by default
setIsGeneratingKeys(true);
setOfferData('');
setShowOfferStep(false);
setShowQRCode(false);
@@ -3005,6 +2911,8 @@
id: Date.now(),
timestamp: Date.now()
}]);
} finally {
setIsGeneratingKeys(false);
}
};
@@ -3031,15 +2939,26 @@
let offer;
try {
console.log('DEBUG: Processing offer input:', offerInput.trim().substring(0, 100) + '...');
console.log('DEBUG: decodeAnyPayload available:', typeof window.decodeAnyPayload === 'function');
console.log('DEBUG: decompressIfNeeded available:', typeof window.decompressIfNeeded === 'function');
// Prefer binary decode first, then gzip JSON
if (typeof window.decodeAnyPayload === 'function') {
console.log('DEBUG: Using decodeAnyPayload...');
const any = window.decodeAnyPayload(offerInput.trim());
console.log('DEBUG: decodeAnyPayload result type:', typeof any);
console.log('DEBUG: decodeAnyPayload result:', any);
offer = (typeof any === 'string') ? JSON.parse(any) : any;
} else {
console.log('DEBUG: Using decompressIfNeeded...');
const rawText = (typeof window.decompressIfNeeded === 'function') ? window.decompressIfNeeded(offerInput.trim()) : offerInput.trim();
console.log('DEBUG: decompressIfNeeded result:', rawText.substring(0, 100) + '...');
offer = JSON.parse(rawText);
}
console.log('DEBUG: Final offer:', offer);
} catch (parseError) {
console.error('DEBUG: Parse error:', parseError);
throw new Error(`Invalid invitation format: ${parseError.message}`);
}
@@ -3117,7 +3036,7 @@
}
// Mark answer as created for state management
if (e.target.value.trim().length > 0) {
if (answerInput.trim().length > 0) {
if (typeof markAnswerCreated === 'function') {
markAnswerCreated();
}
@@ -3316,11 +3235,47 @@
}
};
const handleVerifyConnection = (isValid) => {
const handleVerifyConnection = async (isValid) => {
if (isValid) {
webrtcManagerRef.current.confirmVerification();
// Mark local verification as confirmed
setLocalVerificationConfirmed(true);
// Initialize notification integration if permission was granted
try {
if (window.NotificationIntegration && webrtcManagerRef.current && !notificationIntegrationRef.current) {
const integration = new window.NotificationIntegration(webrtcManagerRef.current);
await integration.init();
// Store reference for cleanup
notificationIntegrationRef.current = integration;
// Check if permission was already granted
const status = integration.getStatus();
if (status.permission === 'granted') {
setMessages(prev => [...prev, {
message: '✓ Notifications enabled - you will receive alerts when the tab is inactive',
type: 'system',
id: Date.now(),
timestamp: Date.now()
}]);
} else {
setMessages(prev => [...prev, {
message: ' Notifications disabled - you can enable them using the button on the main page',
type: 'system',
id: Date.now(),
timestamp: Date.now()
}]);
}
} else if (notificationIntegrationRef.current) {
} else {
// Handle error silently
}
} catch (error) {
console.warn('Failed to initialize notifications:', error);
// Don't show error to user, notifications are optional
}
} else {
setMessages(prev => [...prev, {
message: ' Verification rejected. The connection is unsafe! Session reset..',
@@ -3338,8 +3293,8 @@
// Reset UI to initial state
setConnectionStatus('disconnected');
setOfferData(null);
setAnswerData(null);
setOfferData('');
setAnswerData('');
setOfferInput('');
setAnswerInput('');
setShowOfferStep(false);
@@ -3394,6 +3349,7 @@
setOfferInput('');
setAnswerInput('');
setShowOfferStep(false);
setIsGeneratingKeys(false);
if (!shouldPreserveAnswerData()) {
setShowAnswerStep(false);
@@ -3438,6 +3394,7 @@
};
const handleDisconnect = () => {
try {
setSessionTimeLeft(0);
// Mark as user-initiated disconnect
@@ -3446,11 +3403,18 @@
isUserInitiatedDisconnect: true
});
// Cleanup session state
// Cleanup WebRTC connection
if (webrtcManagerRef.current) {
webrtcManagerRef.current.disconnect();
}
// Cleanup notification integration
if (notificationIntegrationRef.current) {
notificationIntegrationRef.current.cleanup();
notificationIntegrationRef.current = null;
}
// Clear all connection-related states
setKeyFingerprint('');
setVerificationCode('');
setSecurityLevel(null);
@@ -3463,45 +3427,50 @@
setRemoteVerificationConfirmed(false);
setBothVerificationsConfirmed(false);
// Reset UI to initial state (user-initiated disconnect always clears data)
setConnectionStatus('disconnected');
setShowVerification(false);
setOfferData(null);
setAnswerData(null);
// Reset UI to initial state
setOfferData('');
setAnswerData('');
setOfferInput('');
setAnswerInput('');
setShowOfferStep(false);
setShowAnswerStep(false);
setKeyFingerprint('');
setVerificationCode('');
setSecurityLevel(null);
setIsVerified(false);
setIsGeneratingKeys(false);
setShowQRCode(false);
setQrCodeUrl('');
setShowQRScanner(false);
setShowQRScannerModal(false);
// Clear messages
setMessages([]);
// Clear console
if (typeof console.clear === 'function') {
console.clear();
}
// Dispatch disconnect events
document.dispatchEvent(new CustomEvent('peer-disconnect'));
document.dispatchEvent(new CustomEvent('disconnected'));
// Dispatch session cleanup event
document.dispatchEvent(new CustomEvent('session-cleanup', {
detail: {
timestamp: Date.now(),
reason: 'manual_disconnect'
}
}));
// Clear data and reset session timer
handleClearData();
setTimeout(() => {
setSessionTimeLeft(0);
}, 500);
handleClearData();
setTimeout(() => {
// Session manager removed - all features enabled by default
}, 1000);
console.log('Disconnect completed successfully');
} catch (error) {
console.error('Error during disconnect:', error);
}
};
const handleSessionActivated = (session) => {
@@ -3703,7 +3672,10 @@
nextQrFrame: nextQrFrame,
prevQrFrame: prevQrFrame,
// PAKE passwords removed - using SAS verification instead
markAnswerCreated: markAnswerCreated
markAnswerCreated: markAnswerCreated,
notificationIntegrationRef: notificationIntegrationRef,
isGeneratingKeys: isGeneratingKeys,
handleCreateOffer: handleCreateOffer
})
),
@@ -3783,17 +3755,6 @@
])
]),
// Bluetooth Key Transfer Modal
showBluetoothTransfer && window.BluetoothKeyTransfer && React.createElement(window.BluetoothKeyTransfer, {
key: 'bluetooth-transfer-modal',
webrtcManager: webrtcManagerRef.current,
onKeyReceived: handleBluetoothKeyReceived,
onStatusChange: handleBluetoothStatusChange,
onError: handleBluetoothError,
onAutoConnection: handleBluetoothAutoConnection,
isVisible: showBluetoothTransfer,
onClose: () => setShowBluetoothTransfer(false)
})
]);
};
@@ -3824,6 +3785,7 @@
if (!window.initializeApp) {
window.initializeApp = initializeApp;
}
}
};
// Render Enhanced Application
ReactDOM.render(React.createElement(EnhancedSecureP2PChat), document.getElementById('root'));
-514
View File
@@ -1,514 +0,0 @@
/**
* Bluetooth Key Transfer UI Component
*
* Provides user interface for Bluetooth key exchange:
* - Device discovery and connection
* - Key transmission status
* - Error handling and fallbacks
* - Integration with existing QR/manual methods
*/
const BluetoothKeyTransfer = ({
webrtcManager,
onKeyReceived,
onStatusChange,
onAutoConnection,
isVisible = false,
onClose
}) => {
const [bluetoothManager, setBluetoothManager] = React.useState(null);
const [isSupported, setIsSupported] = React.useState(false);
const [isAvailable, setIsAvailable] = React.useState(false);
const [isScanning, setIsScanning] = React.useState(false);
const [isAdvertising, setIsAdvertising] = React.useState(false);
const [connectedDevices, setConnectedDevices] = React.useState([]);
const [status, setStatus] = React.useState('idle');
const [error, setError] = React.useState(null);
const [logs, setLogs] = React.useState([]);
// Initialize Bluetooth manager
React.useEffect(() => {
if (isVisible && !bluetoothManager) {
initializeBluetooth();
}
}, [isVisible]);
// Cleanup on unmount
React.useEffect(() => {
return () => {
if (bluetoothManager) {
bluetoothManager.cleanup();
}
};
}, [bluetoothManager]);
const initializeBluetooth = async () => {
try {
const manager = new window.BluetoothKeyTransfer(
webrtcManager,
handleStatusChange,
handleKeyReceived,
handleError,
handleAutoConnection
);
setBluetoothManager(manager);
// Check support after initialization
setTimeout(() => {
setIsSupported(manager.isSupported);
setIsAvailable(manager.isAvailable);
}, 100);
} catch (error) {
console.error('Failed to initialize Bluetooth manager:', error);
setError('Failed to initialize Bluetooth: ' + error.message);
}
};
const handleStatusChange = (statusType, data) => {
setStatus(statusType);
addLog(`Status: ${statusType}`, data);
// Update UI state based on status
switch (statusType) {
case 'bluetooth_ready':
setIsSupported(data.supported);
setIsAvailable(data.available);
break;
case 'scanning_active':
setIsScanning(true);
break;
case 'scanning_stopped':
setIsScanning(false);
break;
case 'advertising_active':
setIsAdvertising(true);
break;
case 'advertising_stopped':
setIsAdvertising(false);
break;
case 'connected':
setConnectedDevices(prev => [...prev, {
id: data.deviceId,
name: data.deviceName,
connected: true
}]);
break;
}
onStatusChange?.(statusType, data);
};
const handleKeyReceived = (keyData, deviceId) => {
addLog('Key received from device', { deviceId });
onKeyReceived?.(keyData, deviceId);
};
const handleError = (error) => {
console.error('Bluetooth error:', error);
setError(error.message);
addLog('Error', error.message);
};
const handleAutoConnection = (connectionData) => {
console.log('Auto connection completed:', connectionData);
addLog('Auto Connection Completed', connectionData);
onAutoConnection?.(connectionData);
};
const addLog = (message, data = null) => {
const timestamp = new Date().toLocaleTimeString();
const logEntry = {
timestamp,
message,
data: data ? JSON.stringify(data, null, 2) : null
};
setLogs(prev => [...prev.slice(-9), logEntry]); // Keep last 10 logs
};
const startScanning = async () => {
try {
setError(null);
await bluetoothManager.startScanning();
} catch (error) {
setError('Failed to start scanning: ' + error.message);
}
};
const stopScanning = async () => {
try {
await bluetoothManager.stopScanning();
} catch (error) {
setError('Failed to stop scanning: ' + error.message);
}
};
const startAdvertising = async () => {
try {
setError(null);
if (!webrtcManager || !webrtcManager.ecdhKeyPair) {
throw new Error('No public key available for advertising');
}
await bluetoothManager.startAdvertising(
webrtcManager.ecdhKeyPair.publicKey,
'SecureBit Device'
);
} catch (error) {
setError('Failed to start advertising: ' + error.message);
}
};
const stopAdvertising = async () => {
try {
await bluetoothManager.stopAdvertising();
} catch (error) {
setError('Failed to stop advertising: ' + error.message);
}
};
const sendPublicKey = async (deviceId) => {
try {
setError(null);
if (!webrtcManager || !webrtcManager.ecdhKeyPair) {
throw new Error('No public key available for sending');
}
await bluetoothManager.sendPublicKey(
webrtcManager.ecdhKeyPair.publicKey,
deviceId
);
} catch (error) {
setError('Failed to send public key: ' + error.message);
}
};
const clearLogs = () => {
setLogs([]);
};
const startAutoConnection = async (deviceId) => {
try {
setError(null);
await bluetoothManager.startAutoConnection(deviceId);
} catch (error) {
setError('Failed to start auto connection: ' + error.message);
}
};
const startAutoConnectionAsResponder = async (deviceId) => {
try {
setError(null);
await bluetoothManager.startAutoConnectionAsResponder(deviceId);
} catch (error) {
setError('Failed to start auto connection as responder: ' + error.message);
}
};
if (!isVisible) return null;
return React.createElement('div', {
className: 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4'
}, [
React.createElement('div', {
key: 'modal',
className: 'bg-gray-900 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden'
}, [
// Header
React.createElement('div', {
key: 'header',
className: 'flex items-center justify-between p-6 border-b border-gray-700'
}, [
React.createElement('div', {
key: 'title',
className: 'flex items-center space-x-3'
}, [
React.createElement('i', {
key: 'icon',
className: 'fas fa-bluetooth text-blue-400 text-xl'
}),
React.createElement('h2', {
key: 'text',
className: 'text-xl font-semibold text-white'
}, 'Bluetooth Key Transfer')
]),
React.createElement('button', {
key: 'close',
onClick: onClose,
className: 'text-gray-400 hover:text-white transition-colors'
}, [
React.createElement('i', {
className: 'fas fa-times text-xl'
})
])
]),
// Content
React.createElement('div', {
key: 'content',
className: 'p-6 space-y-6 overflow-y-auto max-h-[calc(90vh-200px)]'
}, [
// Status Section
React.createElement('div', {
key: 'status',
className: 'space-y-4'
}, [
React.createElement('h3', {
key: 'title',
className: 'text-lg font-medium text-white'
}, 'Bluetooth Status'),
React.createElement('div', {
key: 'indicators',
className: 'grid grid-cols-2 gap-4'
}, [
React.createElement('div', {
key: 'support',
className: 'flex items-center space-x-2'
}, [
React.createElement('div', {
className: `w-3 h-3 rounded-full ${isSupported ? 'bg-green-500' : 'bg-red-500'}`
}),
React.createElement('span', {
className: 'text-sm text-gray-300'
}, 'Bluetooth Supported')
]),
React.createElement('div', {
key: 'availability',
className: 'flex items-center space-x-2'
}, [
React.createElement('div', {
className: `w-3 h-3 rounded-full ${isAvailable ? 'bg-green-500' : 'bg-red-500'}`
}),
React.createElement('span', {
className: 'text-sm text-gray-300'
}, 'Bluetooth Available')
])
])
]),
// Controls Section
React.createElement('div', {
key: 'controls',
className: 'space-y-4'
}, [
React.createElement('h3', {
key: 'title',
className: 'text-lg font-medium text-white'
}, 'Key Exchange'),
React.createElement('div', {
key: 'buttons',
className: 'grid grid-cols-1 sm:grid-cols-2 gap-4'
}, [
// Scanning Controls
React.createElement('div', {
key: 'scanning',
className: 'space-y-2'
}, [
React.createElement('h4', {
key: 'title',
className: 'text-sm font-medium text-gray-300'
}, 'Discover Devices'),
React.createElement('button', {
key: 'scan',
onClick: isScanning ? stopScanning : startScanning,
disabled: !isSupported || !isAvailable,
className: `w-full px-4 py-2 rounded-lg font-medium transition-colors ${
isScanning
? 'bg-red-600 hover:bg-red-700 text-white'
: 'bg-blue-600 hover:bg-blue-700 text-white disabled:bg-gray-600 disabled:cursor-not-allowed'
}`
}, [
React.createElement('i', {
key: 'icon',
className: `fas ${isScanning ? 'fa-stop' : 'fa-search'} mr-2`
}),
isScanning ? 'Stop Scanning' : 'Start Scanning'
])
]),
// Advertising Controls
React.createElement('div', {
key: 'advertising',
className: 'space-y-2'
}, [
React.createElement('h4', {
key: 'title',
className: 'text-sm font-medium text-gray-300'
}, 'Share Your Key'),
React.createElement('button', {
key: 'advertise',
onClick: isAdvertising ? stopAdvertising : startAdvertising,
disabled: !isSupported || !isAvailable,
className: `w-full px-4 py-2 rounded-lg font-medium transition-colors ${
isAdvertising
? 'bg-red-600 hover:bg-red-700 text-white'
: 'bg-green-600 hover:bg-green-700 text-white disabled:bg-gray-600 disabled:cursor-not-allowed'
}`
}, [
React.createElement('i', {
key: 'icon',
className: `fas ${isAdvertising ? 'fa-stop' : 'fa-broadcast-tower'} mr-2`
}),
isAdvertising ? 'Stop Sharing' : 'Start Sharing'
])
])
])
]),
// Connected Devices
connectedDevices.length > 0 && React.createElement('div', {
key: 'devices',
className: 'space-y-4'
}, [
React.createElement('h3', {
key: 'title',
className: 'text-lg font-medium text-white'
}, 'Connected Devices'),
React.createElement('div', {
key: 'list',
className: 'space-y-2'
}, connectedDevices.map(device =>
React.createElement('div', {
key: device.id,
className: 'flex items-center justify-between p-3 bg-gray-800 rounded-lg'
}, [
React.createElement('div', {
key: 'info',
className: 'flex items-center space-x-3'
}, [
React.createElement('i', {
key: 'icon',
className: 'fas fa-mobile-alt text-blue-400'
}),
React.createElement('span', {
key: 'name',
className: 'text-white'
}, device.name)
]),
React.createElement('div', {
key: 'buttons',
className: 'flex space-x-2'
}, [
React.createElement('button', {
key: 'auto-connect',
onClick: () => startAutoConnection(device.id),
className: 'px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors'
}, 'Auto Connect'),
React.createElement('button', {
key: 'auto-respond',
onClick: () => startAutoConnectionAsResponder(device.id),
className: 'px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white text-sm rounded transition-colors'
}, 'Auto Respond'),
React.createElement('button', {
key: 'send',
onClick: () => sendPublicKey(device.id),
className: 'px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors'
}, 'Send Key')
])
])
))
]),
// Error Display
error && React.createElement('div', {
key: 'error',
className: 'p-4 bg-red-900 border border-red-700 rounded-lg'
}, [
React.createElement('div', {
key: 'header',
className: 'flex items-center space-x-2 mb-2'
}, [
React.createElement('i', {
key: 'icon',
className: 'fas fa-exclamation-triangle text-red-400'
}),
React.createElement('h4', {
key: 'title',
className: 'text-red-400 font-medium'
}, 'Error')
]),
React.createElement('p', {
key: 'message',
className: 'text-red-300 text-sm'
}, error)
]),
// Logs Section
React.createElement('div', {
key: 'logs',
className: 'space-y-4'
}, [
React.createElement('div', {
key: 'header',
className: 'flex items-center justify-between'
}, [
React.createElement('h3', {
key: 'title',
className: 'text-lg font-medium text-white'
}, 'Activity Log'),
React.createElement('button', {
key: 'clear',
onClick: clearLogs,
className: 'text-sm text-gray-400 hover:text-white transition-colors'
}, 'Clear')
]),
React.createElement('div', {
key: 'log-list',
className: 'bg-gray-800 rounded-lg p-4 max-h-40 overflow-y-auto'
}, logs.length === 0 ?
React.createElement('p', {
key: 'empty',
className: 'text-gray-400 text-sm text-center'
}, 'No activity yet') :
logs.map((log, index) =>
React.createElement('div', {
key: index,
className: 'text-xs text-gray-300 mb-1'
}, [
React.createElement('span', {
key: 'time',
className: 'text-gray-500'
}, `[${log.timestamp}] `),
React.createElement('span', {
key: 'message',
className: 'text-gray-300'
}, log.message),
log.data && React.createElement('pre', {
key: 'data',
className: 'text-gray-400 mt-1 ml-4'
}, log.data)
])
)
)
])
]),
// Footer
React.createElement('div', {
key: 'footer',
className: 'flex items-center justify-between p-6 border-t border-gray-700'
}, [
React.createElement('div', {
key: 'info',
className: 'text-sm text-gray-400'
}, 'Bluetooth key exchange provides secure device-to-device communication'),
React.createElement('button', {
key: 'close-footer',
onClick: onClose,
className: 'px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors'
}, 'Close')
])
])
]);
};
// Export component
if (typeof window !== 'undefined') {
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
}
+1 -1
View File
@@ -557,7 +557,7 @@ const EnhancedMinimalHeader = ({
React.createElement('p', {
key: 'subtitle',
className: 'text-xs sm:text-sm text-muted hidden sm:block'
}, 'End-to-end freedom v4.3.120')
}, 'End-to-end freedom v4.4.18')
])
]),
+1 -1
View File
@@ -75,7 +75,7 @@ function Roadmap() {
// current and future phases
{
version: "v4.3.120",
version: "v4.4.18",
title: "Enhanced Security Edition",
status: "current",
date: "Now",
+120 -17
View File
@@ -224,13 +224,18 @@ class EnhancedSecureCryptoUtils {
// Generate secure password for data exchange
static generateSecurePassword() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?';
const charCount = chars.length;
const length = 32;
const randomValues = new Uint32Array(length);
crypto.getRandomValues(randomValues);
let password = '';
// Use rejection sampling to avoid bias
for (let i = 0; i < length; i++) {
password += chars[randomValues[i] % chars.length];
let randomValue;
do {
randomValue = crypto.getRandomValues(new Uint32Array(1))[0];
} while (randomValue >= 4294967296 - (4294967296 % charCount)); // Reject biased values
password += chars[randomValue % charCount];
}
return password;
}
@@ -1204,12 +1209,22 @@ class EnhancedSecureCryptoUtils {
// Verify ECDSA signature (P-384 or P-256)
static async verifySignature(publicKey, signature, data) {
try {
console.log('DEBUG: verifySignature called with:', {
publicKey: publicKey,
signature: signature,
data: data
});
const encoder = new TextEncoder();
const dataBuffer = typeof data === 'string' ? encoder.encode(data) : data;
const signatureBuffer = new Uint8Array(signature);
console.log('DEBUG: verifySignature dataBuffer:', dataBuffer);
console.log('DEBUG: verifySignature signatureBuffer:', signatureBuffer);
// Try SHA-384 first, fallback to SHA-256
try {
console.log('DEBUG: Trying SHA-384 verification...');
const isValid = await crypto.subtle.verify(
{
name: 'ECDSA',
@@ -1220,6 +1235,8 @@ class EnhancedSecureCryptoUtils {
dataBuffer
);
console.log('DEBUG: SHA-384 verification result:', isValid);
EnhancedSecureCryptoUtils.secureLog.log('info', 'Signature verification completed (SHA-384)', {
isValid,
dataSize: dataBuffer.length
@@ -1227,8 +1244,10 @@ class EnhancedSecureCryptoUtils {
return isValid;
} catch (sha384Error) {
console.log('DEBUG: SHA-384 verification failed, trying SHA-256:', sha384Error);
EnhancedSecureCryptoUtils.secureLog.log('warn', 'SHA-384 verification failed, trying SHA-256', { error: sha384Error.message });
console.log('DEBUG: Trying SHA-256 verification...');
const isValid = await crypto.subtle.verify(
{
name: 'ECDSA',
@@ -1239,6 +1258,8 @@ class EnhancedSecureCryptoUtils {
dataBuffer
);
console.log('DEBUG: SHA-256 verification result:', isValid);
EnhancedSecureCryptoUtils.secureLog.log('info', 'Signature verification completed (SHA-256 fallback)', {
isValid,
dataSize: dataBuffer.length
@@ -1583,6 +1604,12 @@ class EnhancedSecureCryptoUtils {
// Import and verify signed public key
static async importSignedPublicKey(signedPackage, verifyingKey, expectedKeyType = 'ECDH') {
try {
console.log('DEBUG: importSignedPublicKey called with:', {
signedPackage: signedPackage,
verifyingKey: verifyingKey,
expectedKeyType: expectedKeyType
});
// Validate package structure
if (!signedPackage || typeof signedPackage !== 'object') {
throw new Error('Invalid signed package format');
@@ -1609,7 +1636,11 @@ class EnhancedSecureCryptoUtils {
// Verify signature
const packageCopy = { keyType, keyData, timestamp, version };
const packageString = JSON.stringify(packageCopy);
console.log('DEBUG: Web version package string for verification:', packageString);
console.log('DEBUG: Web version signature to verify:', signature);
console.log('DEBUG: Web version verifying key:', verifyingKey);
const isValidSignature = await EnhancedSecureCryptoUtils.verifySignature(verifyingKey, signature, packageString);
console.log('DEBUG: Web version signature verification result:', isValidSignature);
if (!isValidSignature) {
throw new Error('Invalid signature on key package - possible MITM attack');
@@ -2296,11 +2327,19 @@ class EnhancedSecureCryptoUtils {
// Generate verification code for out-of-band authentication
static generateVerificationCode() {
const chars = '0123456789ABCDEF';
const charCount = chars.length;
let result = '';
const values = crypto.getRandomValues(new Uint8Array(6));
// Use rejection sampling to avoid bias
for (let i = 0; i < 6; i++) {
result += chars[values[i] % chars.length];
let randomByte;
do {
randomByte = crypto.getRandomValues(new Uint8Array(1))[0];
} while (randomByte >= 256 - (256 % charCount)); // Reject biased values
result += chars[randomByte % charCount];
}
return result.match(/.{1,2}/g).join('-');
}
@@ -2484,22 +2523,86 @@ class EnhancedSecureCryptoUtils {
}
}
// Enhanced input sanitization
// Enhanced input sanitization with iterative processing to handle edge cases
static sanitizeMessage(message) {
if (typeof message !== 'string') {
throw new Error('Message must be a string');
}
return message
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/javascript:/gi, '')
.replace(/data:/gi, '')
.replace(/vbscript:/gi, '')
.replace(/onload\s*=/gi, '')
.replace(/onerror\s*=/gi, '')
.replace(/onclick\s*=/gi, '')
.trim()
.substring(0, 2000); // Increased limit
// Helper function to apply replacement until stable
function replaceUntilStable(str, pattern, replacement = '') {
let previous;
do {
previous = str;
str = str.replace(pattern, replacement);
} while (str !== previous);
return str;
}
// Define all dangerous patterns that need to be removed
const dangerousPatterns = [
// Script tags with various formats
/<script\b[^>]*>[\s\S]*?<\/script\s*>/gi,
/<script\b[^>]*>[\s\S]*?<\/script\s+[^>]*>/gi,
/<script\b[^>]*>[\s\S]*$/gi,
// Other dangerous tags
/<iframe\b[^>]*>[\s\S]*?<\/iframe\s*>/gi,
/<object\b[^>]*>[\s\S]*?<\/object\s*>/gi,
/<embed\b[^>]*>/gi,
/<applet\b[^>]*>[\s\S]*?<\/applet\s*>/gi,
/<style\b[^>]*>[\s\S]*?<\/style\s*>/gi,
// Dangerous protocols
/javascript\s*:/gi,
/data\s*:/gi,
/vbscript\s*:/gi,
// Event handlers
/on\w+\s*=/gi,
// HTML comments
/<!--[\s\S]*?-->/g,
// Link and meta tags with javascript
/<link\b[^>]*javascript[^>]*>/gi,
/<meta\b[^>]*javascript[^>]*>/gi,
// Any remaining script-like content
/<[^>]*script[^>]*>/gi,
/<[^>]*on\w+\s*=[^>]*>/gi
];
// Iterative sanitization to handle edge cases
let sanitized = message;
let previousLength;
let iterations = 0;
const maxIterations = 10; // Prevent infinite loops
do {
previousLength = sanitized.length;
// Apply all dangerous patterns with stable replacement
for (const pattern of dangerousPatterns) {
sanitized = replaceUntilStable(sanitized, pattern);
}
// Additional cleanup for edge cases - each applied until stable
sanitized = replaceUntilStable(sanitized, /<[^>]*>/g);
sanitized = replaceUntilStable(sanitized, /^\w+:/gi);
sanitized = replaceUntilStable(sanitized, /\bon\w+\s*=\s*["'][^"']*["']/gi);
sanitized = replaceUntilStable(sanitized, /\bon\w+\s*=\s*[^>\s]+/gi);
// Single character removal is inherently safe
sanitized = sanitized.replace(/[<>]/g, '').trim();
iterations++;
} while (sanitized.length !== previousLength && iterations < maxIterations);
// Final security pass with stable replacements
sanitized = replaceUntilStable(sanitized, /<[^>]*>/g);
sanitized = replaceUntilStable(sanitized, /^\w+:/gi);
sanitized = replaceUntilStable(sanitized, /\bon\w+\s*=\s*["'][^"']*["']/gi);
sanitized = replaceUntilStable(sanitized, /\bon\w+\s*=\s*[^>\s]+/gi);
// Final single character cleanup
sanitized = sanitized.replace(/[<>]/g, '').trim();
return sanitized.substring(0, 2000); // Limit length
}
// Generate cryptographically secure salt (64 bytes for enhanced security)
+134 -48
View File
@@ -404,12 +404,13 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
// 3. Fake Traffic Generation
this.fakeTrafficConfig = {
enabled: this._config.fakeTraffic.enabled,
minInterval: this._config.fakeTraffic.minInterval,
maxInterval: this._config.fakeTraffic.maxInterval,
minSize: this._config.fakeTraffic.minSize,
maxSize: this._config.fakeTraffic.maxSize,
patterns: this._config.fakeTraffic.patterns
enabled: this._config.fakeTraffic?.enabled || false,
minInterval: this._config.fakeTraffic?.minInterval || 15000,
maxInterval: this._config.fakeTraffic?.maxInterval || 30000,
minSize: this._config.fakeTraffic?.minSize || 64,
maxSize: this._config.fakeTraffic?.maxSize || 1024,
patterns: this._config.fakeTraffic?.patterns || ['heartbeat', 'status', 'ping'],
randomDecoyIntervals: this._config.fakeTraffic?.randomDecoyIntervals || true
};
this.fakeTrafficTimer = null;
this.lastFakeTraffic = 0;
@@ -1769,8 +1770,20 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
// Malicious pattern detection
this._maliciousPatterns = [
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, // Script tags
/javascript:/gi, // JavaScript protocol
// Enhanced script tag detection that handles edge cases
/<script\b[^>]*>[\s\S]*?<\/script\s*>/gi, // Standard </script>
/<script\b[^>]*>[\s\S]*?<\/script\s+[^>]*>/gi, // </script with attributes>
/<script\b[^>]*>[\s\S]*$/gi, // Malformed script tags without closing
// Additional dangerous tags
/<iframe\b[^>]*>[\s\S]*?<\/iframe\s*>/gi, // iframe tags
/<object\b[^>]*>[\s\S]*?<\/object\s*>/gi, // object tags
/<embed\b[^>]*>/gi, // embed tags
/<applet\b[^>]*>[\s\S]*?<\/applet\s*>/gi, // applet tags
/<style\b[^>]*>[\s\S]*?<\/style\s*>/gi, // style tags
// Dangerous protocols
/javascript\s*:/gi, // JavaScript protocol
/data\s*:/gi, // Data protocol
/vbscript\s*:/gi, // VBScript protocol
/data:text\/html/gi, // Data URLs with HTML
/on\w+\s*=/gi, // Event handlers
/eval\s*\(/gi, // eval() calls
@@ -3620,7 +3633,14 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
const dv = new DataView(bits);
const n = (dv.getUint32(0) ^ dv.getUint32(4)) >>> 0;
const sasCode = String(n % 10_000_000).padStart(7, '0');
// Use rejection sampling to avoid bias in SAS code generation
let sasValue;
do {
sasValue = crypto.getRandomValues(new Uint32Array(1))[0];
} while (sasValue >= 4294967296 - (4294967296 % 10_000_000));
const sasCode = String(sasValue % 10_000_000).padStart(7, '0');
this._secureLog('info', 'SAS code computed successfully', {
@@ -4461,25 +4481,65 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
}
}
// Generate fingerprint mask for anti-fingerprinting with enhanced randomization
generateFingerprintMask() {
// Enhanced randomization to prevent side-channel attacks
const cryptoRandom = crypto.getRandomValues(new Uint8Array(128));
const mask = {
timingOffset: cryptoRandom[0] % 1000 + cryptoRandom[1] % 500, // 0-1500ms
sizeVariation: (cryptoRandom[2] % 50 + 75) / 100, // 0.75 to 1.25
noisePattern: Array.from(crypto.getRandomValues(new Uint8Array(64))), // Increased size
headerVariations: [
'X-Client-Version', 'X-Session-ID', 'X-Request-ID', 'X-Timestamp', 'X-Signature',
'X-Secure', 'X-Encrypted', 'X-Protected', 'X-Safe', 'X-Anonymous', 'X-Private'
],
noiseIntensity: cryptoRandom[3] % 100 + 50, // 50-150%
sizeMultiplier: (cryptoRandom[4] % 50 + 75) / 100, // 0.75-1.25
timingVariation: cryptoRandom[5] % 1000 + 100 // 100-1100ms
};
return mask;
}
// Helper function: unbiased integer in [min, max]
getSafeRandomInt(min, max) {
if (!Number.isInteger(min) || !Number.isInteger(max)) {
throw new Error('getSafeRandomInt requires integer min and max');
}
if (min >= max) {
throw new Error('min must be less than max');
}
const range = max - min + 1;
const bitsNeeded = Math.ceil(Math.log2(range));
const bytesNeeded = Math.ceil(bitsNeeded / 8);
const mask = (1 << bitsNeeded) - 1;
let randomValue;
do {
const randomBytes = crypto.getRandomValues(new Uint8Array(bytesNeeded));
randomValue = 0;
for (let i = 0; i < bytesNeeded; i++) {
randomValue = (randomValue * 256) + randomBytes[i];
}
randomValue = randomValue & mask;
} while (randomValue >= range);
return min + randomValue;
}
getSafeRandomFloat(minFloat, maxFloat, steps = 1000) {
if (typeof minFloat !== 'number' || typeof maxFloat !== 'number') {
throw new Error('getSafeRandomFloat requires numeric min and max');
}
if (minFloat >= maxFloat) {
throw new Error('minFloat must be less than maxFloat');
}
const randomIndex = this.getSafeRandomInt(0, steps);
const step = (maxFloat - minFloat) / steps;
return minFloat + (randomIndex * step);
}
generateFingerprintMask() {
const mask = {
timingOffset: this.getSafeRandomInt(0, 1500),
sizeVariation: this.getSafeRandomFloat(0.75, 1.25, 1000),
noisePattern: Array.from(crypto.getRandomValues(new Uint8Array(64))),
headerVariations: [
'X-Client-Version', 'X-Session-ID', 'X-Request-ID', 'X-Timestamp', 'X-Signature',
'X-Secure', 'X-Encrypted', 'X-Protected', 'X-Safe', 'X-Anonymous', 'X-Private'
],
noiseIntensity: this.getSafeRandomInt(50, 150),
sizeMultiplier: this.getSafeRandomFloat(0.75, 1.25, 1000),
timingVariation: this.getSafeRandomInt(100, 1100)
};
return mask;
}
// Security configuration - all features enabled by default
configureSecurityForSession() {
@@ -4914,8 +4974,7 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
// FIX: Increase intervals to reduce load
const nextInterval = this.fakeTrafficConfig.randomDecoyIntervals ?
Math.random() * (this.fakeTrafficConfig.maxInterval - this.fakeTrafficConfig.minInterval) +
this.fakeTrafficConfig.minInterval :
this.getUnbiasedRandomInRange(this.fakeTrafficConfig.minInterval, Math.min(this.fakeTrafficConfig.maxInterval, 60000)) : // Cap at 60 seconds
this.fakeTrafficConfig.minInterval;
// Minimum interval 15 seconds for stability
@@ -4931,7 +4990,10 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
};
// Start fake traffic generation with longer initial delay
const initialDelay = Math.random() * this.fakeTrafficConfig.maxInterval + EnhancedSecureWebRTCManager.TIMEOUTS.DECOY_INITIAL_DELAY; // Add 5 seconds minimum
// Use a reasonable range for initial delay (5-30 seconds)
const minDelay = EnhancedSecureWebRTCManager.TIMEOUTS.DECOY_INITIAL_DELAY;
const maxDelay = Math.min(this.fakeTrafficConfig.maxInterval, 30000); // Cap at 30 seconds
const initialDelay = this.getUnbiasedRandomInRange(minDelay, maxDelay);
this.fakeTrafficTimer = setTimeout(sendFakeMessage, initialDelay);
}
@@ -4943,13 +5005,10 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
}
generateFakeMessage() {
const pattern = this.fakeTrafficConfig.patterns[
Math.floor(Math.random() * this.fakeTrafficConfig.patterns.length)
];
const patternIndex = this.getUnbiasedRandomInRange(0, this.fakeTrafficConfig.patterns.length - 1);
const pattern = this.fakeTrafficConfig.patterns[patternIndex];
const size = Math.floor(Math.random() *
(this.fakeTrafficConfig.maxSize - this.fakeTrafficConfig.minSize + 1)) +
this.fakeTrafficConfig.minSize;
const size = this.getUnbiasedRandomInRange(this.fakeTrafficConfig.minSize, this.fakeTrafficConfig.maxSize);
const fakeData = crypto.getRandomValues(new Uint8Array(size));
@@ -5550,7 +5609,7 @@ async processOrderedPackets() {
addNoise(data) {
const dataArray = new Uint8Array(data);
const noiseSize = Math.floor(Math.random() * 32) + 8; // 8-40 bytes
const noiseSize = this.getUnbiasedRandomInRange(8, 40); // 8-40 bytes
const noise = crypto.getRandomValues(new Uint8Array(noiseSize));
const result = new Uint8Array(dataArray.length + noiseSize);
@@ -5595,12 +5654,12 @@ async processOrderedPackets() {
addRandomHeaders(data) {
const dataArray = new Uint8Array(data);
const headerCount = Math.floor(Math.random() * 3) + 1; // 1-3 headers
const headerCount = this.getUnbiasedRandomInRange(1, 3); // 1-3 headers
let totalHeaderSize = 0;
// Calculate total header size
for (let i = 0; i < headerCount; i++) {
totalHeaderSize += 4 + Math.floor(Math.random() * 16) + 4; // size + data + checksum
totalHeaderSize += 4 + this.getUnbiasedRandomInRange(0, 15) + 4; // size + data + checksum
}
const result = new Uint8Array(totalHeaderSize + dataArray.length);
@@ -5608,10 +5667,21 @@ async processOrderedPackets() {
// Add random headers
for (let i = 0; i < headerCount; i++) {
const headerName = this.fingerprintMask.headerVariations[
Math.floor(Math.random() * this.fingerprintMask.headerVariations.length)
];
const headerData = crypto.getRandomValues(new Uint8Array(Math.floor(Math.random() * 16) + 4));
// Generate unbiased random index for header selection
let headerIndex;
do {
headerIndex = crypto.getRandomValues(new Uint8Array(1))[0];
} while (headerIndex >= 256 - (256 % this.fingerprintMask.headerVariations.length));
const headerName = this.fingerprintMask.headerVariations[headerIndex % this.fingerprintMask.headerVariations.length];
// Generate unbiased random size for header data (4-19 bytes)
let headerSize;
do {
headerSize = crypto.getRandomValues(new Uint8Array(1))[0];
} while (headerSize >= 256 - (256 % 16));
const headerData = crypto.getRandomValues(new Uint8Array((headerSize % 16) + 4));
// Header structure: [size:4][name:4][data:variable][checksum:4]
const headerView = new DataView(result.buffer, offset);
@@ -11084,6 +11154,10 @@ async processMessage(data) {
throw new Error('Connection lost during message preparation');
}
// Note: master key session is managed by SecureMasterKeyManager
// Do not gate here on _isUnlocked to avoid false blocking
// Session timers are handled inside the master key manager on key access
// Validate keys inside critical section
if (!this.encryptionKey || !this.macKey || !this.metadataKey) {
throw new Error('Encryption keys not initialized');
@@ -11140,7 +11214,19 @@ async processMessage(data) {
operationId: operationId,
errorType: error.constructor.name
});
throw error;
// Improved user-facing error messages (English)
if (error.message.includes('Session expired')) {
throw new Error('Session expired. Please enter your password to unlock.');
} else if (error.message.includes('Encryption keys not initialized')) {
throw new Error('Session expired due to inactivity. Please reconnect to the chat.');
} else if (error.message.includes('Connection lost')) {
throw new Error('Connection lost. Please check your Internet connection.');
} else if (error.message.includes('Rate limit exceeded')) {
throw new Error('Message rate limit exceeded. Please wait before sending another message.');
} else {
throw error;
}
}
}, 2000); // Reduced timeout for crypto operations
}
@@ -11496,7 +11582,7 @@ async processMessage(data) {
if (error.message.includes('Connection not ready')) {
throw new Error('Connection not ready for file transfer. Check connection status.');
} else if (error.message.includes('Encryption keys not initialized')) {
throw new Error('Encryption keys not initialized. Try reconnecting.');
throw new Error('Session expired due to inactivity. Please reconnect to the chat.');
} else if (error.message.includes('Transfer timeout')) {
throw new Error('File transfer timeout. Check connection and try again.');
} else {
@@ -13023,8 +13109,8 @@ class SecureMasterKeyManager {
this._lastActivity = null;
// Configuration
this._sessionTimeoutMs = 15 * 60 * 1000; // 15 minutes
this._inactivityTimeoutMs = 5 * 60 * 1000; // 5 minutes
this._sessionTimeoutMs = 60 * 60 * 1000; // 60 minutes (увеличено с 15 минут)
this._inactivityTimeoutMs = 30 * 60 * 1000; // 30 minutes (увеличено с 5 минут)
// PBKDF2 parameters
this._pbkdf2Iterations = 100000; // 100k iterations
@@ -0,0 +1,279 @@
/**
* Notification Integration Module for SecureBit WebRTC Chat
* Integrates secure notifications with existing WebRTC architecture
*
* @version 1.0.0
* @author SecureBit Team
* @license MIT
*/
import { SecureChatNotificationManager } from './SecureNotificationManager.js';
class NotificationIntegration {
constructor(webrtcManager) {
this.webrtcManager = webrtcManager;
this.notificationManager = new SecureChatNotificationManager({
maxQueueSize: 10,
rateLimitMs: 1000, // Reduced from 2000ms to 1000ms
trustedOrigins: [
window.location.origin,
// Add other trusted origins for CDN icons
]
});
this.isInitialized = false;
this.originalOnMessage = null;
this.originalOnStatusChange = null;
this.processedMessages = new Set(); // Track processed messages to avoid duplicates
}
/**
* Initialize notification integration
* @returns {Promise<boolean>} Initialization success
*/
async init() {
try {
if (this.isInitialized) {
return true;
}
// Store original callbacks
this.originalOnMessage = this.webrtcManager.onMessage;
this.originalOnStatusChange = this.webrtcManager.onStatusChange;
// Wrap the original onMessage callback
this.webrtcManager.onMessage = (message, type) => {
this.handleIncomingMessage(message, type);
// Call original callback if it exists
if (this.originalOnMessage) {
this.originalOnMessage(message, type);
}
};
// Wrap the original onStatusChange callback
this.webrtcManager.onStatusChange = (status) => {
this.handleStatusChange(status);
// Call original callback if it exists
if (this.originalOnStatusChange) {
this.originalOnStatusChange(status);
}
};
// Also hook into the deliverMessageToUI method if it exists
if (this.webrtcManager.deliverMessageToUI) {
this.originalDeliverMessageToUI = this.webrtcManager.deliverMessageToUI.bind(this.webrtcManager);
this.webrtcManager.deliverMessageToUI = (message, type) => {
this.handleIncomingMessage(message, type);
this.originalDeliverMessageToUI(message, type);
};
}
this.isInitialized = true;
return true;
} catch (error) {
return false;
}
}
/**
* Handle incoming messages and trigger notifications
* @param {*} message - Message content
* @param {string} type - Message type
* @private
*/
handleIncomingMessage(message, type) {
try {
// Create a unique key for this message to avoid duplicates
const messageKey = `${type}:${typeof message === 'string' ? message : JSON.stringify(message)}`;
// Skip if we've already processed this message
if (this.processedMessages.has(messageKey)) {
return;
}
// Mark message as processed
this.processedMessages.add(messageKey);
// Clean up old processed messages (keep only last 100)
if (this.processedMessages.size > 100) {
const messagesArray = Array.from(this.processedMessages);
this.processedMessages.clear();
messagesArray.slice(-50).forEach(msg => this.processedMessages.add(msg));
}
// Only process chat messages, not system messages
if (type === 'system' || type === 'file-transfer' || type === 'heartbeat') {
return;
}
// Extract message information
const messageInfo = this.extractMessageInfo(message, type);
if (!messageInfo) {
return;
}
// Send notification
const notificationResult = this.notificationManager.notify(
messageInfo.senderName,
messageInfo.text,
{
icon: messageInfo.senderAvatar,
senderId: messageInfo.senderId,
onClick: (senderId) => {
this.focusChatWindow();
}
}
);
} catch (error) {
// Handle error silently
}
}
/**
* Handle status changes
* @param {string} status - Connection status
* @private
*/
handleStatusChange(status) {
try {
// Clear notifications when connection is lost
if (status === 'disconnected' || status === 'failed') {
this.notificationManager.clearNotificationQueue();
this.notificationManager.resetUnreadCount();
}
} catch (error) {
// Handle error silently
}
}
/**
* Extract message information for notifications
* @param {*} message - Message content
* @param {string} type - Message type
* @returns {Object|null} Extracted message info or null
* @private
*/
extractMessageInfo(message, type) {
try {
let messageData = message;
// Handle different message formats
if (typeof message === 'string') {
try {
messageData = JSON.parse(message);
} catch (e) {
// Plain text message
return {
senderName: 'Peer',
text: message,
senderId: 'peer',
senderAvatar: null
};
}
}
// Handle structured message data
if (typeof messageData === 'object' && messageData !== null) {
return {
senderName: messageData.senderName || messageData.name || 'Peer',
text: messageData.text || messageData.message || messageData.content || '',
senderId: messageData.senderId || messageData.id || 'peer',
senderAvatar: messageData.senderAvatar || messageData.avatar || null
};
}
return null;
} catch (error) {
return null;
}
}
/**
* Focus chat window when notification is clicked
* @private
*/
focusChatWindow() {
try {
window.focus();
// Scroll to bottom of messages if container exists
const messagesContainer = document.getElementById('messages');
if (messagesContainer) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
} catch (error) {
// Handle error silently
}
}
/**
* Request notification permission
* @returns {Promise<boolean>} Permission granted status
*/
async requestPermission() {
try {
return await this.notificationManager.requestPermission();
} catch (error) {
return false;
}
}
/**
* Get notification status
* @returns {Object} Notification status
*/
getStatus() {
return this.notificationManager.getStatus();
}
/**
* Clear all notifications
*/
clearNotifications() {
this.notificationManager.clearNotificationQueue();
this.notificationManager.resetUnreadCount();
}
/**
* Cleanup integration
*/
cleanup() {
try {
if (this.isInitialized) {
// Restore original callbacks
if (this.originalOnMessage) {
this.webrtcManager.onMessage = this.originalOnMessage;
}
if (this.originalOnStatusChange) {
this.webrtcManager.onStatusChange = this.originalOnStatusChange;
}
if (this.originalDeliverMessageToUI) {
this.webrtcManager.deliverMessageToUI = this.originalDeliverMessageToUI;
}
// Clear notifications
this.clearNotifications();
this.isInitialized = false;
}
} catch (error) {
// Handle error silently
}
}
}
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = { NotificationIntegration };
}
// Global export for browser usage
if (typeof window !== 'undefined') {
window.NotificationIntegration = NotificationIntegration;
}
@@ -0,0 +1,613 @@
/**
* Secure and Reliable Notification Manager for P2P WebRTC Chat
* Follows best practices: OWASP, MDN, Chrome DevRel
*
* @version 1.0.0
* @author SecureBit Team
* @license MIT
*/
class SecureChatNotificationManager {
constructor(config = {}) {
// Safely read Notification permission (iOS Safari may not define Notification)
this.permission = (typeof Notification !== 'undefined' && Notification && typeof Notification.permission === 'string')
? Notification.permission
: 'denied';
this.isTabActive = this.checkTabActive(); // Initialize with proper check
this.unreadCount = 0;
this.originalTitle = document.title;
this.notificationQueue = [];
this.maxQueueSize = config.maxQueueSize || 5;
this.rateLimitMs = config.rateLimitMs || 2000; // Spam protection
this.lastNotificationTime = 0;
this.trustedOrigins = config.trustedOrigins || [];
// Secure context flag
this.isSecureContext = window.isSecureContext;
// Cross-browser compatibility for Page Visibility API
this.hidden = this.getHiddenProperty();
this.visibilityChange = this.getVisibilityChangeEvent();
this.initVisibilityTracking();
this.initSecurityChecks();
}
/**
* Initialize security checks and validation
* @private
*/
initSecurityChecks() {
// Security checks are performed silently
}
/**
* Get hidden property name for cross-browser compatibility
* @returns {string} Hidden property name
* @private
*/
getHiddenProperty() {
if (typeof document.hidden !== "undefined") {
return "hidden";
} else if (typeof document.msHidden !== "undefined") {
return "msHidden";
} else if (typeof document.webkitHidden !== "undefined") {
return "webkitHidden";
}
return "hidden"; // fallback
}
/**
* Get visibility change event name for cross-browser compatibility
* @returns {string} Visibility change event name
* @private
*/
getVisibilityChangeEvent() {
if (typeof document.hidden !== "undefined") {
return "visibilitychange";
} else if (typeof document.msHidden !== "undefined") {
return "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
return "webkitvisibilitychange";
}
return "visibilitychange"; // fallback
}
/**
* Check if tab is currently active using multiple methods
* @returns {boolean} True if tab is active
* @private
*/
checkTabActive() {
// Primary method: Page Visibility API
if (this.hidden && typeof document[this.hidden] !== "undefined") {
return !document[this.hidden];
}
// Fallback method: document.hasFocus()
if (typeof document.hasFocus === "function") {
return document.hasFocus();
}
// Ultimate fallback: assume active
return true;
}
/**
* Initialize page visibility tracking (Page Visibility API)
* @private
*/
initVisibilityTracking() {
// Primary method: Page Visibility API with cross-browser support
if (typeof document.addEventListener !== "undefined" && typeof document[this.hidden] !== "undefined") {
document.addEventListener(this.visibilityChange, () => {
this.isTabActive = this.checkTabActive();
if (this.isTabActive) {
this.resetUnreadCount();
this.clearNotificationQueue();
}
});
}
// Fallback method: Window focus/blur events
window.addEventListener('focus', () => {
this.isTabActive = this.checkTabActive();
if (this.isTabActive) {
this.resetUnreadCount();
}
});
window.addEventListener('blur', () => {
this.isTabActive = this.checkTabActive();
});
// Page unload cleanup
window.addEventListener('beforeunload', () => {
this.clearNotificationQueue();
});
}
/**
* Request notification permission (BEST PRACTICE: Only call in response to user action)
* Never call on page load!
* @returns {Promise<boolean>} Permission granted status
*/
async requestPermission() {
// Secure context check
if (!this.isSecureContext || !('Notification' in window)) {
return false;
}
if (this.permission === 'granted') {
return true;
}
if (this.permission === 'denied') {
return false;
}
try {
this.permission = await Notification.requestPermission();
return this.permission === 'granted';
} catch (error) {
return false;
}
}
/**
* Update page title with unread count
* @private
*/
updateTitle() {
if (this.unreadCount > 0) {
document.title = `(${this.unreadCount}) ${this.originalTitle}`;
} else {
document.title = this.originalTitle;
}
}
/**
* XSS Protection: Sanitize input text
* @param {string} text - Text to sanitize
* @returns {string} Sanitized text
* @private
*/
sanitizeText(text) {
if (typeof text !== 'string') {
return '';
}
// Remove HTML tags and potentially dangerous characters
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;')
.substring(0, 500); // Length limit
}
/**
* Validate icon URL (XSS protection)
* @param {string} url - URL to validate
* @returns {string|null} Validated URL or null
* @private
*/
validateIconUrl(url) {
if (!url) return null;
try {
const parsedUrl = new URL(url, window.location.origin);
// Only allow HTTPS and data URLs
if (parsedUrl.protocol === 'https:' || parsedUrl.protocol === 'data:') {
// Check trusted origins if specified
if (this.trustedOrigins.length > 0) {
const isTrusted = this.trustedOrigins.some(origin =>
parsedUrl.origin === origin
);
return isTrusted ? parsedUrl.href : null;
}
return parsedUrl.href;
}
return null;
} catch (error) {
return null;
}
}
/**
* Rate limiting for spam protection
* @returns {boolean} Rate limit check passed
* @private
*/
checkRateLimit() {
const now = Date.now();
if (now - this.lastNotificationTime < this.rateLimitMs) {
return false;
}
this.lastNotificationTime = now;
return true;
}
/**
* Send secure notification
* @param {string} senderName - Name of message sender
* @param {string} message - Message content
* @param {Object} options - Notification options
* @returns {Notification|null} Created notification or null
*/
notify(senderName, message, options = {}) {
// Abort if Notifications API is not available (e.g., iOS Safari)
if (typeof Notification === 'undefined') {
return null;
}
// Update tab active state before checking
this.isTabActive = this.checkTabActive();
// Only show if tab is NOT active (user is on another tab or minimized)
if (this.isTabActive) {
return null;
}
// Permission check
if (this.permission !== 'granted') {
return null;
}
// Rate limiting
if (!this.checkRateLimit()) {
return null;
}
// Data sanitization (XSS Protection)
const safeSenderName = this.sanitizeText(senderName || 'Unknown');
const safeMessage = this.sanitizeText(message || '');
const safeIcon = this.validateIconUrl(options.icon) || '/logo/icon-192x192.png';
// Queue overflow protection
if (this.notificationQueue.length >= this.maxQueueSize) {
this.clearNotificationQueue();
}
try {
const notification = new Notification(
`${safeSenderName}`,
{
body: safeMessage.substring(0, 200), // Length limit
icon: safeIcon,
badge: safeIcon,
tag: `chat-${options.senderId || 'unknown'}`, // Grouping
requireInteraction: false, // Don't block user
silent: options.silent || false,
// Vibrate only for mobile and if supported
vibrate: navigator.vibrate ? [200, 100, 200] : undefined,
// Safe metadata
data: {
senderId: this.sanitizeText(options.senderId),
timestamp: Date.now(),
// Don't include sensitive data!
}
}
);
// Increment counter
this.unreadCount++;
this.updateTitle();
// Add to queue for management
this.notificationQueue.push(notification);
// Safe click handler
notification.onclick = (event) => {
event.preventDefault(); // Prevent default behavior
window.focus();
notification.close();
// Safe callback
if (typeof options.onClick === 'function') {
try {
options.onClick(options.senderId);
} catch (error) {
console.error('[Notifications] Error in onClick handler:', error);
}
}
};
// Error handler
notification.onerror = (event) => {
console.error('[Notifications] Error showing notification:', event);
};
// Auto-close after reasonable time
const autoCloseTimeout = Math.min(options.autoClose || 5000, 10000);
setTimeout(() => {
notification.close();
this.removeFromQueue(notification);
}, autoCloseTimeout);
return notification;
} catch (error) {
console.error('[Notifications] Failed to create notification:', error);
return null;
}
}
/**
* Remove notification from queue
* @param {Notification} notification - Notification to remove
* @private
*/
removeFromQueue(notification) {
const index = this.notificationQueue.indexOf(notification);
if (index > -1) {
this.notificationQueue.splice(index, 1);
}
}
/**
* Clear all notifications
*/
clearNotificationQueue() {
this.notificationQueue.forEach(notification => {
try {
notification.close();
} catch (error) {
// Ignore errors when closing
}
});
this.notificationQueue = [];
}
/**
* Reset unread counter
*/
resetUnreadCount() {
this.unreadCount = 0;
this.updateTitle();
}
/**
* Get current status
* @returns {Object} Current notification status
*/
getStatus() {
return {
permission: this.permission,
isTabActive: this.isTabActive,
unreadCount: this.unreadCount,
isSecureContext: this.isSecureContext,
queueSize: this.notificationQueue.length
};
}
}
/**
* Secure integration with WebRTC
*/
class SecureP2PChat {
constructor() {
this.notificationManager = new SecureChatNotificationManager({
maxQueueSize: 5,
rateLimitMs: 2000,
trustedOrigins: [
window.location.origin,
// Add other trusted origins for CDN icons
]
});
this.dataChannel = null;
this.peerConnection = null;
this.remotePeerName = 'Peer';
this.messageHistory = [];
this.maxHistorySize = 100;
}
/**
* Initialize when user connects
*/
async init() {
// Initialize notification manager silently
}
/**
* Method for manual permission request (called on click)
* @returns {Promise<boolean>} Permission granted status
*/
async enableNotifications() {
const granted = await this.notificationManager.requestPermission();
return granted;
}
/**
* Setup DataChannel with security checks
* @param {RTCDataChannel} dataChannel - WebRTC data channel
*/
setupDataChannel(dataChannel) {
if (!dataChannel) {
console.error('[Chat] Invalid DataChannel');
return;
}
this.dataChannel = dataChannel;
// Setup handlers
this.dataChannel.onmessage = (event) => {
this.handleIncomingMessage(event.data);
};
this.dataChannel.onerror = (error) => {
// Handle error silently
};
}
/**
* XSS Protection: Validate incoming messages
* @param {string|Object} data - Message data
* @returns {Object|null} Validated message or null
* @private
*/
validateMessage(data) {
try {
const message = typeof data === 'string' ? JSON.parse(data) : data;
// Check message structure
if (!message || typeof message !== 'object') {
throw new Error('Invalid message structure');
}
// Check required fields
if (!message.text || typeof message.text !== 'string') {
throw new Error('Invalid message text');
}
// Message length limit (DoS protection)
if (message.text.length > 10000) {
throw new Error('Message too long');
}
return {
text: message.text,
senderName: message.senderName || 'Unknown',
senderId: message.senderId || 'unknown',
timestamp: message.timestamp || Date.now(),
senderAvatar: message.senderAvatar || null
};
} catch (error) {
console.error('[Chat] Message validation failed:', error);
return null;
}
}
/**
* Secure handling of incoming messages
* @param {string|Object} data - Message data
* @private
*/
handleIncomingMessage(data) {
const message = this.validateMessage(data);
if (!message) {
return;
}
// Save to history (with limit)
this.messageHistory.push(message);
if (this.messageHistory.length > this.maxHistorySize) {
this.messageHistory.shift();
}
// Display in UI (with sanitization)
this.displayMessage(message);
// Send notification only if tab is inactive
this.notificationManager.notify(
message.senderName,
message.text,
{
icon: message.senderAvatar,
senderId: message.senderId,
onClick: (senderId) => {
this.scrollToLatestMessage();
}
}
);
// Optional: sound (with check)
if (!this.notificationManager.isTabActive) {
this.playNotificationSound();
}
}
/**
* XSS Protection: Safe message display
* @param {Object} message - Message to display
* @private
*/
displayMessage(message) {
const container = document.getElementById('messages');
if (!container) {
return;
}
const messageEl = document.createElement('div');
messageEl.className = 'message';
// Use textContent to prevent XSS
const nameEl = document.createElement('strong');
nameEl.textContent = message.senderName + ': ';
const textEl = document.createElement('span');
textEl.textContent = message.text;
const timeEl = document.createElement('small');
timeEl.textContent = new Date(message.timestamp).toLocaleTimeString();
messageEl.appendChild(nameEl);
messageEl.appendChild(textEl);
messageEl.appendChild(document.createElement('br'));
messageEl.appendChild(timeEl);
container.appendChild(messageEl);
this.scrollToLatestMessage();
}
/**
* Safe sound playback
* @private
*/
playNotificationSound() {
try {
// Use only local audio files
const audio = new Audio('/assets/audio/notification.mp3');
audio.volume = 0.3; // Moderate volume
// Error handling
audio.play().catch(error => {
// Handle audio error silently
});
} catch (error) {
// Handle audio creation error silently
}
}
/**
* Scroll to latest message
* @private
*/
scrollToLatestMessage() {
const container = document.getElementById('messages');
if (container) {
container.scrollTop = container.scrollHeight;
}
}
/**
* Get status
* @returns {Object} Current chat status
*/
getStatus() {
return {
notifications: this.notificationManager.getStatus(),
messageCount: this.messageHistory.length,
connected: this.dataChannel?.readyState === 'open'
};
}
}
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = { SecureChatNotificationManager, SecureP2PChat };
}
// Global export for browser usage
if (typeof window !== 'undefined') {
window.SecureChatNotificationManager = SecureChatNotificationManager;
window.SecureP2PChat = SecureP2PChat;
}
+1 -1
View File
@@ -96,7 +96,7 @@ class PWAInstallPrompt {
setupEventListeners() {
window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault();
// Don't prevent default - let browser show its own banner
this.deferredPrompt = event;
if (this.checkInstallationStatus()) {
+2 -3
View File
@@ -1,7 +1,7 @@
import { EnhancedSecureCryptoUtils } from '../crypto/EnhancedSecureCryptoUtils.js';
import { EnhancedSecureWebRTCManager } from '../network/EnhancedSecureWebRTCManager.js';
import { EnhancedSecureFileTransfer } from '../transfer/EnhancedSecureFileTransfer.js';
import BluetoothKeyTransfer from '../transfer/BluetoothKeyTransfer.js';
import { NotificationIntegration } from '../notifications/NotificationIntegration.js';
// Import UI components (side-effect: they attach themselves to window.*)
import '../components/ui/Header.jsx';
@@ -12,13 +12,12 @@ import '../components/ui/Testimonials.jsx';
import '../components/ui/ComparisonTable.jsx';
import '../components/ui/Roadmap.jsx';
import '../components/ui/FileTransfer.jsx';
import '../components/ui/BluetoothKeyTransfer.jsx';
// Expose to global for legacy usage inside app code
window.EnhancedSecureCryptoUtils = EnhancedSecureCryptoUtils;
window.EnhancedSecureWebRTCManager = EnhancedSecureWebRTCManager;
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
window.NotificationIntegration = NotificationIntegration;
// Mount application once DOM and modules are ready
const start = () => {
+25 -24
View File
@@ -1,13 +1,15 @@
// Temporary bootstrap that still uses eval for JSX components fetched as text.
// Next step is to replace this with proper ESM imports of prebuilt JS.
// Bootstrap that loads modules using dynamic ESM imports.
// This approach is CSP-compliant and doesn't use eval().
(async () => {
try {
const timestamp = Date.now();
const [cryptoModule, webrtcModule, fileTransferModule, bluetoothModule] = await Promise.all([
const [cryptoModule, webrtcModule, fileTransferModule, notificationModule, notificationTestModule, notificationGestureTestModule] = await Promise.all([
import(`../crypto/EnhancedSecureCryptoUtils.js?v=${timestamp}`),
import(`../network/EnhancedSecureWebRTCManager.js?v=${timestamp}`),
import(`../transfer/EnhancedSecureFileTransfer.js?v=${timestamp}`),
import(`../transfer/BluetoothKeyTransfer.js?v=${timestamp}`),
import(`../notifications/NotificationIntegration.js?v=${timestamp}`),
import(`../notifications/NotificationTest.js?v=${timestamp}`),
import(`../notifications/NotificationGestureTest.js?v=${timestamp}`),
]);
const { EnhancedSecureCryptoUtils } = cryptoModule;
@@ -16,29 +18,28 @@
window.EnhancedSecureWebRTCManager = EnhancedSecureWebRTCManager;
const { EnhancedSecureFileTransfer } = fileTransferModule;
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
const { default: BluetoothKeyTransfer } = bluetoothModule;
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
const { NotificationIntegration } = notificationModule;
window.NotificationIntegration = NotificationIntegration;
const { NotificationTest } = notificationTestModule;
window.NotificationTest = NotificationTest;
const { NotificationGestureTest } = notificationGestureTestModule;
window.NotificationGestureTest = NotificationGestureTest;
async function loadReactComponent(path) {
const response = await fetch(`${path}?v=${timestamp}`);
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
const code = await response.text();
// eslint-disable-next-line no-eval
eval(code);
}
await Promise.all([
loadReactComponent('../components/ui/Header.jsx'),
loadReactComponent('../components/ui/DownloadApps.jsx'),
loadReactComponent('../components/ui/ComparisonTable.jsx'),
loadReactComponent('../components/ui/UniqueFeatureSlider.jsx'),
loadReactComponent('../components/ui/SecurityFeatures.jsx'),
loadReactComponent('../components/ui/Testimonials.jsx'),
loadReactComponent('../components/ui/Roadmap.jsx'),
loadReactComponent('../components/ui/FileTransfer.jsx'),
loadReactComponent('../components/ui/BluetoothKeyTransfer.jsx'),
// Load React components using dynamic imports instead of eval
const componentModules = await Promise.all([
import(`../components/ui/Header.jsx?v=${timestamp}`),
import(`../components/ui/DownloadApps.jsx?v=${timestamp}`),
import(`../components/ui/ComparisonTable.jsx?v=${timestamp}`),
import(`../components/ui/UniqueFeatureSlider.jsx?v=${timestamp}`),
import(`../components/ui/SecurityFeatures.jsx?v=${timestamp}`),
import(`../components/ui/Testimonials.jsx?v=${timestamp}`),
import(`../components/ui/Roadmap.jsx?v=${timestamp}`),
import(`../components/ui/FileTransfer.jsx?v=${timestamp}`),
]);
// Components are automatically registered on window by their respective modules
console.log('✅ All React components loaded successfully');
if (typeof window.initializeApp === 'function') {
window.initializeApp();
} else {
+1 -1
View File
@@ -48,7 +48,7 @@ window.showUpdateNotification = function showUpdateNotification() {
<i class="fas fa-download text-lg"></i>
<div class="flex-1">
<div class="font-medium">Update Available</div>
<div class="text-sm opacity-90">SecureBit.chat v4.3.120 - ECDH + DTLS + SAS is ready</div>
<div class="text-sm opacity-90">SecureBit.chat v4.4.18 - ECDH + DTLS + SAS is ready</div>
</div>
<button data-action="reload" class="bg-white/20 hover:bg-white/30 px-3 py-1 rounded text-sm font-medium transition-colors">
Update
-771
View File
@@ -1,771 +0,0 @@
/**
* Bluetooth Key Transfer Module for SecureBit.chat
*
* Features:
* - Secure Bluetooth Low Energy (BLE) key exchange
* - Automatic device discovery and pairing
* - Encrypted key transmission
* - Fallback to manual/QR methods
* - Cross-platform compatibility
*
* Security:
* - Uses BLE advertising for device discovery
* - Encrypts key data before transmission
* - Implements secure pairing protocols
* - Validates received keys before acceptance
*/
class BluetoothKeyTransfer {
constructor(webrtcManager, onStatusChange, onKeyReceived, onError, onAutoConnection) {
this.webrtcManager = webrtcManager;
this.onStatusChange = onStatusChange;
this.onKeyReceived = onKeyReceived;
this.onError = onError;
this.onAutoConnection = onAutoConnection;
// Bluetooth state
this.isSupported = false;
this.isAvailable = false;
this.isScanning = false;
this.isAdvertising = false;
this.connectedDevices = new Map();
this.advertisingData = null;
// Service and characteristic UUIDs
this.SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e'; // Nordic UART Service
this.TX_CHARACTERISTIC_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'; // TX Characteristic
this.RX_CHARACTERISTIC_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'; // RX Characteristic
// Key transfer protocol
this.PROTOCOL_VERSION = '1.0';
this.MAX_CHUNK_SIZE = 20; // BLE characteristic max size
this.TRANSFER_TIMEOUT = 30000; // 30 seconds
this.init();
}
async init() {
try {
// Check Bluetooth support
if (!navigator.bluetooth) {
this.log('warn', 'Bluetooth API not supported in this browser');
return;
}
this.isSupported = true;
// Check if Bluetooth is available
const available = await navigator.bluetooth.getAvailability();
this.isAvailable = available;
if (!available) {
this.log('warn', 'Bluetooth is not available on this device');
return;
}
this.log('info', 'Bluetooth Key Transfer initialized successfully');
this.onStatusChange?.('bluetooth_ready', { supported: true, available: true });
} catch (error) {
this.log('error', 'Failed to initialize Bluetooth Key Transfer', error);
this.onError?.(error);
}
}
// ============================================
// PUBLIC METHODS
// ============================================
/**
* Start advertising this device for key exchange
*/
async startAdvertising(publicKey, deviceName = 'SecureBit Device') {
if (!this.isSupported || !this.isAvailable) {
throw new Error('Bluetooth not supported or available');
}
try {
this.log('info', 'Starting Bluetooth advertising...');
this.onStatusChange?.('advertising_starting', { deviceName });
// Prepare advertising data
const keyData = await this.prepareKeyData(publicKey);
this.advertisingData = {
deviceName,
keyData,
timestamp: Date.now(),
protocolVersion: this.PROTOCOL_VERSION
};
// Start advertising
const options = {
filters: [{
services: [this.SERVICE_UUID]
}],
optionalServices: [this.SERVICE_UUID]
};
this.isAdvertising = true;
this.onStatusChange?.('advertising_active', { deviceName });
this.log('info', 'Bluetooth advertising started successfully');
return true;
} catch (error) {
this.log('error', 'Failed to start Bluetooth advertising', error);
this.isAdvertising = false;
this.onError?.(error);
throw error;
}
}
/**
* Stop advertising
*/
async stopAdvertising() {
try {
this.isAdvertising = false;
this.advertisingData = null;
this.onStatusChange?.('advertising_stopped');
this.log('info', 'Bluetooth advertising stopped');
} catch (error) {
this.log('error', 'Failed to stop advertising', error);
}
}
/**
* Start scanning for nearby devices
*/
async startScanning() {
if (!this.isSupported || !this.isAvailable) {
throw new Error('Bluetooth not supported or available');
}
try {
this.log('info', 'Starting Bluetooth device scan...');
this.onStatusChange?.('scanning_starting');
const options = {
filters: [{
services: [this.SERVICE_UUID]
}],
optionalServices: [this.SERVICE_UUID]
};
this.isScanning = true;
this.onStatusChange?.('scanning_active');
// Start scanning
const device = await navigator.bluetooth.requestDevice(options);
if (device) {
this.log('info', 'Device selected:', device.name);
await this.connectToDevice(device);
}
} catch (error) {
this.log('error', 'Failed to start scanning', error);
this.isScanning = false;
this.onError?.(error);
throw error;
}
}
/**
* Stop scanning
*/
async stopScanning() {
try {
this.isScanning = false;
this.onStatusChange?.('scanning_stopped');
this.log('info', 'Bluetooth scanning stopped');
} catch (error) {
this.log('error', 'Failed to stop scanning', error);
}
}
/**
* Send public key to connected device
*/
async sendPublicKey(publicKey, deviceId) {
try {
const device = this.connectedDevices.get(deviceId);
if (!device) {
throw new Error('Device not connected');
}
this.log('info', 'Sending public key to device:', deviceId);
this.onStatusChange?.('key_sending', { deviceId });
const keyData = await this.prepareKeyData(publicKey);
await this.sendData(keyData, device);
this.onStatusChange?.('key_sent', { deviceId });
this.log('info', 'Public key sent successfully');
} catch (error) {
this.log('error', 'Failed to send public key', error);
this.onError?.(error);
throw error;
}
}
/**
* Start automatic connection process (offer answer verification)
*/
async startAutoConnection(deviceId) {
try {
this.log('info', 'Starting automatic connection process');
this.onStatusChange?.('auto_connection_starting', { deviceId });
if (!this.webrtcManager) {
throw new Error('WebRTC Manager not available');
}
// Step 1: Create and send offer
this.onStatusChange?.('creating_offer', { deviceId });
const offer = await this.webrtcManager.createSecureOffer();
// Send offer via Bluetooth
await this.sendConnectionData(offer, deviceId, 'offer');
this.onStatusChange?.('offer_sent', { deviceId });
// Step 2: Wait for answer
this.onStatusChange?.('waiting_for_answer', { deviceId });
const answer = await this.waitForConnectionData(deviceId, 'answer');
// Step 3: Process answer
this.onStatusChange?.('processing_answer', { deviceId });
await this.webrtcManager.createSecureAnswer(answer);
// Step 4: Wait for verification
this.onStatusChange?.('waiting_for_verification', { deviceId });
const verification = await this.waitForConnectionData(deviceId, 'verification');
// Step 5: Complete connection
this.onStatusChange?.('completing_connection', { deviceId });
await this.completeConnection(verification, deviceId);
this.onStatusChange?.('auto_connection_complete', { deviceId });
this.log('info', 'Automatic connection completed successfully');
} catch (error) {
this.log('error', 'Automatic connection failed', error);
this.onStatusChange?.('auto_connection_failed', { deviceId, error: error.message });
this.onError?.(error);
throw error;
}
}
/**
* Start automatic connection as responder (wait for offer create answer send verification)
*/
async startAutoConnectionAsResponder(deviceId) {
try {
this.log('info', 'Starting automatic connection as responder');
this.onStatusChange?.('auto_connection_responder_starting', { deviceId });
if (!this.webrtcManager) {
throw new Error('WebRTC Manager not available');
}
// Step 1: Wait for offer
this.onStatusChange?.('waiting_for_offer', { deviceId });
const offer = await this.waitForConnectionData(deviceId, 'offer');
// Step 2: Create and send answer
this.onStatusChange?.('creating_answer', { deviceId });
const answer = await this.webrtcManager.createSecureAnswer(offer);
// Send answer via Bluetooth
await this.sendConnectionData(answer, deviceId, 'answer');
this.onStatusChange?.('answer_sent', { deviceId });
// Step 3: Send verification
this.onStatusChange?.('sending_verification', { deviceId });
const verification = await this.createVerificationData();
await this.sendConnectionData(verification, deviceId, 'verification');
this.onStatusChange?.('auto_connection_responder_complete', { deviceId });
this.log('info', 'Automatic connection as responder completed successfully');
} catch (error) {
this.log('error', 'Automatic connection as responder failed', error);
this.onStatusChange?.('auto_connection_responder_failed', { deviceId, error: error.message });
this.onError?.(error);
throw error;
}
}
// ============================================
// PRIVATE METHODS
// ============================================
/**
* Connect to a discovered device
*/
async connectToDevice(device) {
try {
this.log('info', 'Connecting to device:', device.name);
this.onStatusChange?.('connecting', { deviceName: device.name });
const server = await device.gatt.connect();
const service = await server.getPrimaryService(this.SERVICE_UUID);
// Get characteristics
const txCharacteristic = await service.getCharacteristic(this.TX_CHARACTERISTIC_UUID);
const rxCharacteristic = await service.getCharacteristic(this.RX_CHARACTERISTIC_UUID);
// Set up data reception
rxCharacteristic.addEventListener('characteristicvaluechanged', (event) => {
this.handleReceivedData(event, device.id);
});
await rxCharacteristic.startNotifications();
// Store device connection
this.connectedDevices.set(device.id, {
device,
server,
service,
txCharacteristic,
rxCharacteristic,
connected: true
});
this.onStatusChange?.('connected', { deviceId: device.id, deviceName: device.name });
this.log('info', 'Successfully connected to device:', device.name);
} catch (error) {
this.log('error', 'Failed to connect to device', error);
this.onError?.(error);
throw error;
}
}
/**
* Send data to connected device
*/
async sendData(data, device) {
try {
const { txCharacteristic } = device;
const dataString = JSON.stringify(data);
const chunks = this.chunkString(dataString, this.MAX_CHUNK_SIZE);
// Send chunks sequentially
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const chunkData = new TextEncoder().encode(chunk);
await txCharacteristic.writeValue(chunkData);
// Small delay between chunks
await new Promise(resolve => setTimeout(resolve, 10));
}
this.log('info', `Sent ${chunks.length} chunks to device`);
} catch (error) {
this.log('error', 'Failed to send data', error);
throw error;
}
}
/**
* Handle received data from device
*/
async handleReceivedData(event, deviceId) {
try {
const value = event.target.value;
const data = new TextDecoder().decode(value);
// Try to parse as connection data first
try {
const connectionData = JSON.parse(data);
if (connectionData.type && ['offer', 'answer', 'verification'].includes(connectionData.type)) {
this.handleConnectionData(connectionData, deviceId);
return;
}
} catch (e) {
// Not connection data, continue with key processing
}
// Process received data as key data
const keyData = await this.processReceivedData(data, deviceId);
if (keyData) {
this.onKeyReceived?.(keyData, deviceId);
}
} catch (error) {
this.log('error', 'Failed to handle received data', error);
this.onError?.(error);
}
}
/**
* Handle connection data (offer, answer, verification)
*/
async handleConnectionData(connectionData, deviceId) {
try {
this.log('info', `Received ${connectionData.type} from device:`, deviceId);
// Store connection data for waiting processes
if (!this.connectionDataBuffer) {
this.connectionDataBuffer = new Map();
}
if (!this.connectionDataBuffer.has(deviceId)) {
this.connectionDataBuffer.set(deviceId, new Map());
}
this.connectionDataBuffer.get(deviceId).set(connectionData.type, connectionData);
// Notify waiting processes
this.onStatusChange?.(`${connectionData.type}_received`, { deviceId, data: connectionData });
} catch (error) {
this.log('error', 'Failed to handle connection data', error);
this.onError?.(error);
}
}
/**
* Prepare key data for transmission
*/
async prepareKeyData(publicKey) {
try {
// Export public key
const exportedKey = await crypto.subtle.exportKey('spki', publicKey);
const keyArray = new Uint8Array(exportedKey);
// Create secure payload
const payload = {
type: 'public_key',
key: Array.from(keyArray),
timestamp: Date.now(),
protocolVersion: this.PROTOCOL_VERSION,
deviceId: await this.getDeviceId()
};
// Sign payload for integrity
const signature = await this.signPayload(payload);
payload.signature = signature;
return payload;
} catch (error) {
this.log('error', 'Failed to prepare key data', error);
throw error;
}
}
/**
* Process received key data
*/
async processReceivedData(data, deviceId) {
try {
const payload = JSON.parse(data);
// Validate payload
if (!this.validatePayload(payload)) {
throw new Error('Invalid payload received');
}
// Verify signature
if (!await this.verifyPayload(payload)) {
throw new Error('Payload signature verification failed');
}
// Import public key
const publicKey = await crypto.subtle.importKey(
'spki',
new Uint8Array(payload.key),
{ name: 'ECDH', namedCurve: 'P-384' },
false,
[]
);
this.log('info', 'Successfully processed received key data');
return {
publicKey,
deviceId,
timestamp: payload.timestamp,
protocolVersion: payload.protocolVersion
};
} catch (error) {
this.log('error', 'Failed to process received data', error);
throw error;
}
}
/**
* Sign payload for integrity
*/
async signPayload(payload) {
try {
// Use WebRTC manager's signing key if available
if (this.webrtcManager && this.webrtcManager.signingKeyPair) {
const data = new TextEncoder().encode(JSON.stringify(payload));
const signature = await crypto.subtle.sign(
{ name: 'ECDSA', hash: 'SHA-384' },
this.webrtcManager.signingKeyPair.privateKey,
data
);
return Array.from(new Uint8Array(signature));
}
// Fallback: simple hash
const data = new TextEncoder().encode(JSON.stringify(payload));
const hash = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(hash));
} catch (error) {
this.log('error', 'Failed to sign payload', error);
throw error;
}
}
/**
* Verify payload signature
*/
async verifyPayload(payload) {
try {
const { signature, ...payloadWithoutSig } = payload;
// Use WebRTC manager's signing key if available
if (this.webrtcManager && this.webrtcManager.signingKeyPair) {
const data = new TextEncoder().encode(JSON.stringify(payloadWithoutSig));
const isValid = await crypto.subtle.verify(
{ name: 'ECDSA', hash: 'SHA-384' },
this.webrtcManager.signingKeyPair.publicKey,
new Uint8Array(signature),
data
);
return isValid;
}
// Fallback: simple hash comparison
const data = new TextEncoder().encode(JSON.stringify(payloadWithoutSig));
const hash = await crypto.subtle.digest('SHA-256', data);
const expectedHash = Array.from(new Uint8Array(hash));
return JSON.stringify(signature) === JSON.stringify(expectedHash);
} catch (error) {
this.log('error', 'Failed to verify payload', error);
return false;
}
}
/**
* Validate received payload
*/
validatePayload(payload) {
return (
payload &&
payload.type === 'public_key' &&
payload.key &&
Array.isArray(payload.key) &&
payload.timestamp &&
payload.protocolVersion &&
payload.signature &&
Array.isArray(payload.signature)
);
}
/**
* Get unique device ID
*/
async getDeviceId() {
try {
// Try to get a unique device identifier
if (navigator.userAgentData && navigator.userAgentData.getHighEntropyValues) {
const values = await navigator.userAgentData.getHighEntropyValues(['model']);
return values.model || 'unknown-device';
}
// Fallback to user agent hash
const userAgent = navigator.userAgent;
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(userAgent));
return Array.from(new Uint8Array(hash)).slice(0, 8).map(b => b.toString(16).padStart(2, '0')).join('');
} catch (error) {
return 'unknown-device';
}
}
/**
* Send connection data (offer, answer, verification)
*/
async sendConnectionData(data, deviceId, type) {
try {
const device = this.connectedDevices.get(deviceId);
if (!device) {
throw new Error('Device not connected');
}
const connectionData = {
type: type,
data: data,
timestamp: Date.now(),
protocolVersion: this.PROTOCOL_VERSION
};
await this.sendData(connectionData, device);
this.log('info', `Sent ${type} to device:`, deviceId);
} catch (error) {
this.log('error', `Failed to send ${type}`, error);
throw error;
}
}
/**
* Wait for specific connection data type
*/
async waitForConnectionData(deviceId, type, timeout = 30000) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error(`Timeout waiting for ${type}`));
}, timeout);
const checkForData = () => {
if (this.connectionDataBuffer &&
this.connectionDataBuffer.has(deviceId) &&
this.connectionDataBuffer.get(deviceId).has(type)) {
clearTimeout(timeoutId);
const data = this.connectionDataBuffer.get(deviceId).get(type);
this.connectionDataBuffer.get(deviceId).delete(type);
resolve(data.data);
} else {
setTimeout(checkForData, 100);
}
};
checkForData();
});
}
/**
* Create verification data
*/
async createVerificationData() {
try {
if (!this.webrtcManager || !this.webrtcManager.keyFingerprint) {
throw new Error('WebRTC Manager or key fingerprint not available');
}
return {
fingerprint: this.webrtcManager.keyFingerprint,
verificationCode: this.webrtcManager.verificationCode || 'auto-verified',
timestamp: Date.now()
};
} catch (error) {
this.log('error', 'Failed to create verification data', error);
throw error;
}
}
/**
* Complete connection process
*/
async completeConnection(verification, deviceId) {
try {
// Validate verification data
if (verification.fingerprint && this.webrtcManager.keyFingerprint) {
if (verification.fingerprint !== this.webrtcManager.keyFingerprint) {
throw new Error('Key fingerprint mismatch');
}
}
// Notify auto connection completion
this.onAutoConnection?.({
deviceId,
fingerprint: verification.fingerprint,
verificationCode: verification.verificationCode,
timestamp: Date.now()
});
this.log('info', 'Connection completed successfully');
} catch (error) {
this.log('error', 'Failed to complete connection', error);
throw error;
}
}
/**
* Split string into chunks
*/
chunkString(str, chunkSize) {
const chunks = [];
for (let i = 0; i < str.length; i += chunkSize) {
chunks.push(str.slice(i, i + chunkSize));
}
return chunks;
}
/**
* Logging utility
*/
log(level, message, data = null) {
const timestamp = new Date().toISOString();
const logMessage = `[BluetoothKeyTransfer ${timestamp}] ${message}`;
switch (level) {
case 'error':
console.error(logMessage, data);
break;
case 'warn':
console.warn(logMessage, data);
break;
case 'info':
console.info(logMessage, data);
break;
default:
console.log(logMessage, data);
}
}
// ============================================
// CLEANUP METHODS
// ============================================
/**
* Disconnect from all devices
*/
async disconnectAll() {
try {
for (const [deviceId, device] of this.connectedDevices) {
if (device.connected && device.server) {
device.server.disconnect();
}
}
this.connectedDevices.clear();
this.log('info', 'Disconnected from all devices');
} catch (error) {
this.log('error', 'Failed to disconnect devices', error);
}
}
/**
* Cleanup resources
*/
async cleanup() {
try {
await this.stopAdvertising();
await this.stopScanning();
await this.disconnectAll();
this.log('info', 'Bluetooth Key Transfer cleaned up');
} catch (error) {
this.log('error', 'Failed to cleanup Bluetooth Key Transfer', error);
}
}
}
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = BluetoothKeyTransfer;
} else if (typeof window !== 'undefined') {
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
}
+5 -9
View File
@@ -1,9 +1,9 @@
// SecureBit.chat Service Worker
// Conservative PWA Edition v4.3.120 - Minimal Caching Strategy
// Conservative PWA Edition v4.4.18 - Minimal Caching Strategy
const CACHE_NAME = 'securebit-pwa-v4.3.120';
const STATIC_CACHE = 'securebit-pwa-static-v4.3.120';
const DYNAMIC_CACHE = 'securebit-pwa-dynamic-v4.3.120';
const CACHE_NAME = 'securebit-pwa-v4.4.18';
const STATIC_CACHE = 'securebit-pwa-static-v4.4.18';
const DYNAMIC_CACHE = 'securebit-pwa-dynamic-v4.4.18';
// Essential files for PWA offline functionality
const STATIC_ASSETS = [
@@ -27,11 +27,7 @@ const STATIC_ASSETS = [
'/src/pwa/pwa-manager.js',
'/src/pwa/install-prompt.js',
'/src/scripts/pwa-register.js',
'/src/scripts/pwa-offline-test.js',
// Bluetooth key transfer (PWA feature)
'/src/transfer/BluetoothKeyTransfer.js',
'/src/components/ui/BluetoothKeyTransfer.jsx'
'/src/scripts/pwa-offline-test.js'
];
// Sensitive files that should never be cached
-455
View File
@@ -1,455 +0,0 @@
// src/components/ui/BluetoothKeyTransfer.jsx
var BluetoothKeyTransfer = ({
webrtcManager,
onKeyReceived,
onStatusChange,
onAutoConnection,
isVisible = false,
onClose
}) => {
const [bluetoothManager, setBluetoothManager] = React.useState(null);
const [isSupported, setIsSupported] = React.useState(false);
const [isAvailable, setIsAvailable] = React.useState(false);
const [isScanning, setIsScanning] = React.useState(false);
const [isAdvertising, setIsAdvertising] = React.useState(false);
const [connectedDevices, setConnectedDevices] = React.useState([]);
const [status, setStatus] = React.useState("idle");
const [error, setError] = React.useState(null);
const [logs, setLogs] = React.useState([]);
React.useEffect(() => {
if (isVisible && !bluetoothManager) {
initializeBluetooth();
}
}, [isVisible]);
React.useEffect(() => {
return () => {
if (bluetoothManager) {
bluetoothManager.cleanup();
}
};
}, [bluetoothManager]);
const initializeBluetooth = async () => {
try {
const manager = new window.BluetoothKeyTransfer(
webrtcManager,
handleStatusChange,
handleKeyReceived,
handleError,
handleAutoConnection
);
setBluetoothManager(manager);
setTimeout(() => {
setIsSupported(manager.isSupported);
setIsAvailable(manager.isAvailable);
}, 100);
} catch (error2) {
console.error("Failed to initialize Bluetooth manager:", error2);
setError("Failed to initialize Bluetooth: " + error2.message);
}
};
const handleStatusChange = (statusType, data) => {
setStatus(statusType);
addLog(`Status: ${statusType}`, data);
switch (statusType) {
case "bluetooth_ready":
setIsSupported(data.supported);
setIsAvailable(data.available);
break;
case "scanning_active":
setIsScanning(true);
break;
case "scanning_stopped":
setIsScanning(false);
break;
case "advertising_active":
setIsAdvertising(true);
break;
case "advertising_stopped":
setIsAdvertising(false);
break;
case "connected":
setConnectedDevices((prev) => [...prev, {
id: data.deviceId,
name: data.deviceName,
connected: true
}]);
break;
}
onStatusChange?.(statusType, data);
};
const handleKeyReceived = (keyData, deviceId) => {
addLog("Key received from device", { deviceId });
onKeyReceived?.(keyData, deviceId);
};
const handleError = (error2) => {
console.error("Bluetooth error:", error2);
setError(error2.message);
addLog("Error", error2.message);
};
const handleAutoConnection = (connectionData) => {
console.log("Auto connection completed:", connectionData);
addLog("Auto Connection Completed", connectionData);
onAutoConnection?.(connectionData);
};
const addLog = (message, data = null) => {
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
const logEntry = {
timestamp,
message,
data: data ? JSON.stringify(data, null, 2) : null
};
setLogs((prev) => [...prev.slice(-9), logEntry]);
};
const startScanning = async () => {
try {
setError(null);
await bluetoothManager.startScanning();
} catch (error2) {
setError("Failed to start scanning: " + error2.message);
}
};
const stopScanning = async () => {
try {
await bluetoothManager.stopScanning();
} catch (error2) {
setError("Failed to stop scanning: " + error2.message);
}
};
const startAdvertising = async () => {
try {
setError(null);
if (!webrtcManager || !webrtcManager.ecdhKeyPair) {
throw new Error("No public key available for advertising");
}
await bluetoothManager.startAdvertising(
webrtcManager.ecdhKeyPair.publicKey,
"SecureBit Device"
);
} catch (error2) {
setError("Failed to start advertising: " + error2.message);
}
};
const stopAdvertising = async () => {
try {
await bluetoothManager.stopAdvertising();
} catch (error2) {
setError("Failed to stop advertising: " + error2.message);
}
};
const sendPublicKey = async (deviceId) => {
try {
setError(null);
if (!webrtcManager || !webrtcManager.ecdhKeyPair) {
throw new Error("No public key available for sending");
}
await bluetoothManager.sendPublicKey(
webrtcManager.ecdhKeyPair.publicKey,
deviceId
);
} catch (error2) {
setError("Failed to send public key: " + error2.message);
}
};
const clearLogs = () => {
setLogs([]);
};
const startAutoConnection = async (deviceId) => {
try {
setError(null);
await bluetoothManager.startAutoConnection(deviceId);
} catch (error2) {
setError("Failed to start auto connection: " + error2.message);
}
};
const startAutoConnectionAsResponder = async (deviceId) => {
try {
setError(null);
await bluetoothManager.startAutoConnectionAsResponder(deviceId);
} catch (error2) {
setError("Failed to start auto connection as responder: " + error2.message);
}
};
if (!isVisible) return null;
return React.createElement("div", {
className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
}, [
React.createElement("div", {
key: "modal",
className: "bg-gray-900 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden"
}, [
// Header
React.createElement("div", {
key: "header",
className: "flex items-center justify-between p-6 border-b border-gray-700"
}, [
React.createElement("div", {
key: "title",
className: "flex items-center space-x-3"
}, [
React.createElement("i", {
key: "icon",
className: "fas fa-bluetooth text-blue-400 text-xl"
}),
React.createElement("h2", {
key: "text",
className: "text-xl font-semibold text-white"
}, "Bluetooth Key Transfer")
]),
React.createElement("button", {
key: "close",
onClick: onClose,
className: "text-gray-400 hover:text-white transition-colors"
}, [
React.createElement("i", {
className: "fas fa-times text-xl"
})
])
]),
// Content
React.createElement("div", {
key: "content",
className: "p-6 space-y-6 overflow-y-auto max-h-[calc(90vh-200px)]"
}, [
// Status Section
React.createElement("div", {
key: "status",
className: "space-y-4"
}, [
React.createElement("h3", {
key: "title",
className: "text-lg font-medium text-white"
}, "Bluetooth Status"),
React.createElement("div", {
key: "indicators",
className: "grid grid-cols-2 gap-4"
}, [
React.createElement("div", {
key: "support",
className: "flex items-center space-x-2"
}, [
React.createElement("div", {
className: `w-3 h-3 rounded-full ${isSupported ? "bg-green-500" : "bg-red-500"}`
}),
React.createElement("span", {
className: "text-sm text-gray-300"
}, "Bluetooth Supported")
]),
React.createElement("div", {
key: "availability",
className: "flex items-center space-x-2"
}, [
React.createElement("div", {
className: `w-3 h-3 rounded-full ${isAvailable ? "bg-green-500" : "bg-red-500"}`
}),
React.createElement("span", {
className: "text-sm text-gray-300"
}, "Bluetooth Available")
])
])
]),
// Controls Section
React.createElement("div", {
key: "controls",
className: "space-y-4"
}, [
React.createElement("h3", {
key: "title",
className: "text-lg font-medium text-white"
}, "Key Exchange"),
React.createElement("div", {
key: "buttons",
className: "grid grid-cols-1 sm:grid-cols-2 gap-4"
}, [
// Scanning Controls
React.createElement("div", {
key: "scanning",
className: "space-y-2"
}, [
React.createElement("h4", {
key: "title",
className: "text-sm font-medium text-gray-300"
}, "Discover Devices"),
React.createElement("button", {
key: "scan",
onClick: isScanning ? stopScanning : startScanning,
disabled: !isSupported || !isAvailable,
className: `w-full px-4 py-2 rounded-lg font-medium transition-colors ${isScanning ? "bg-red-600 hover:bg-red-700 text-white" : "bg-blue-600 hover:bg-blue-700 text-white disabled:bg-gray-600 disabled:cursor-not-allowed"}`
}, [
React.createElement("i", {
key: "icon",
className: `fas ${isScanning ? "fa-stop" : "fa-search"} mr-2`
}),
isScanning ? "Stop Scanning" : "Start Scanning"
])
]),
// Advertising Controls
React.createElement("div", {
key: "advertising",
className: "space-y-2"
}, [
React.createElement("h4", {
key: "title",
className: "text-sm font-medium text-gray-300"
}, "Share Your Key"),
React.createElement("button", {
key: "advertise",
onClick: isAdvertising ? stopAdvertising : startAdvertising,
disabled: !isSupported || !isAvailable,
className: `w-full px-4 py-2 rounded-lg font-medium transition-colors ${isAdvertising ? "bg-red-600 hover:bg-red-700 text-white" : "bg-green-600 hover:bg-green-700 text-white disabled:bg-gray-600 disabled:cursor-not-allowed"}`
}, [
React.createElement("i", {
key: "icon",
className: `fas ${isAdvertising ? "fa-stop" : "fa-broadcast-tower"} mr-2`
}),
isAdvertising ? "Stop Sharing" : "Start Sharing"
])
])
])
]),
// Connected Devices
connectedDevices.length > 0 && React.createElement("div", {
key: "devices",
className: "space-y-4"
}, [
React.createElement("h3", {
key: "title",
className: "text-lg font-medium text-white"
}, "Connected Devices"),
React.createElement("div", {
key: "list",
className: "space-y-2"
}, connectedDevices.map(
(device) => React.createElement("div", {
key: device.id,
className: "flex items-center justify-between p-3 bg-gray-800 rounded-lg"
}, [
React.createElement("div", {
key: "info",
className: "flex items-center space-x-3"
}, [
React.createElement("i", {
key: "icon",
className: "fas fa-mobile-alt text-blue-400"
}),
React.createElement("span", {
key: "name",
className: "text-white"
}, device.name)
]),
React.createElement("div", {
key: "buttons",
className: "flex space-x-2"
}, [
React.createElement("button", {
key: "auto-connect",
onClick: () => startAutoConnection(device.id),
className: "px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-sm rounded transition-colors"
}, "Auto Connect"),
React.createElement("button", {
key: "auto-respond",
onClick: () => startAutoConnectionAsResponder(device.id),
className: "px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white text-sm rounded transition-colors"
}, "Auto Respond"),
React.createElement("button", {
key: "send",
onClick: () => sendPublicKey(device.id),
className: "px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors"
}, "Send Key")
])
])
))
]),
// Error Display
error && React.createElement("div", {
key: "error",
className: "p-4 bg-red-900 border border-red-700 rounded-lg"
}, [
React.createElement("div", {
key: "header",
className: "flex items-center space-x-2 mb-2"
}, [
React.createElement("i", {
key: "icon",
className: "fas fa-exclamation-triangle text-red-400"
}),
React.createElement("h4", {
key: "title",
className: "text-red-400 font-medium"
}, "Error")
]),
React.createElement("p", {
key: "message",
className: "text-red-300 text-sm"
}, error)
]),
// Logs Section
React.createElement("div", {
key: "logs",
className: "space-y-4"
}, [
React.createElement("div", {
key: "header",
className: "flex items-center justify-between"
}, [
React.createElement("h3", {
key: "title",
className: "text-lg font-medium text-white"
}, "Activity Log"),
React.createElement("button", {
key: "clear",
onClick: clearLogs,
className: "text-sm text-gray-400 hover:text-white transition-colors"
}, "Clear")
]),
React.createElement(
"div",
{
key: "log-list",
className: "bg-gray-800 rounded-lg p-4 max-h-40 overflow-y-auto"
},
logs.length === 0 ? React.createElement("p", {
key: "empty",
className: "text-gray-400 text-sm text-center"
}, "No activity yet") : logs.map(
(log, index) => React.createElement("div", {
key: index,
className: "text-xs text-gray-300 mb-1"
}, [
React.createElement("span", {
key: "time",
className: "text-gray-500"
}, `[${log.timestamp}] `),
React.createElement("span", {
key: "message",
className: "text-gray-300"
}, log.message),
log.data && React.createElement("pre", {
key: "data",
className: "text-gray-400 mt-1 ml-4"
}, log.data)
])
)
)
])
]),
// Footer
React.createElement("div", {
key: "footer",
className: "flex items-center justify-between p-6 border-t border-gray-700"
}, [
React.createElement("div", {
key: "info",
className: "text-sm text-gray-400"
}, "Bluetooth key exchange provides secure device-to-device communication"),
React.createElement("button", {
key: "close-footer",
onClick: onClose,
className: "px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors"
}, "Close")
])
])
]);
};
if (typeof window !== "undefined") {
window.BluetoothKeyTransfer = BluetoothKeyTransfer;
}
-575
View File
@@ -1,575 +0,0 @@
// src/components/ui/Header.jsx
var EnhancedMinimalHeader = ({
status,
fingerprint,
verificationCode,
onDisconnect,
isConnected,
securityLevel,
webrtcManager
}) => {
const [realSecurityLevel, setRealSecurityLevel] = React.useState(null);
const [lastSecurityUpdate, setLastSecurityUpdate] = React.useState(0);
React.useEffect(() => {
let isUpdating = false;
let lastUpdateAttempt = 0;
const updateRealSecurityStatus = async () => {
const now = Date.now();
if (now - lastUpdateAttempt < 1e4) {
return;
}
if (isUpdating) {
return;
}
isUpdating = true;
lastUpdateAttempt = now;
try {
if (!webrtcManager || !isConnected) {
return;
}
const activeWebrtcManager = webrtcManager;
let realSecurityData = null;
if (typeof activeWebrtcManager.getRealSecurityLevel === "function") {
realSecurityData = await activeWebrtcManager.getRealSecurityLevel();
} else if (typeof activeWebrtcManager.calculateAndReportSecurityLevel === "function") {
realSecurityData = await activeWebrtcManager.calculateAndReportSecurityLevel();
} else {
realSecurityData = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(activeWebrtcManager);
}
if (realSecurityData && realSecurityData.isRealData !== false) {
const currentScore = realSecurityLevel?.score || 0;
const newScore = realSecurityData.score || 0;
if (currentScore !== newScore || !realSecurityLevel) {
setRealSecurityLevel(realSecurityData);
setLastSecurityUpdate(now);
} 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;
}
};
if (isConnected) {
updateRealSecurityStatus();
if (!realSecurityLevel || realSecurityLevel.score < 50) {
const retryInterval = setInterval(() => {
if (!realSecurityLevel || realSecurityLevel.score < 50) {
updateRealSecurityStatus();
} else {
clearInterval(retryInterval);
}
}, 5e3);
setTimeout(() => clearInterval(retryInterval), 3e4);
}
}
const interval = setInterval(updateRealSecurityStatus, 3e4);
return () => clearInterval(interval);
}, [webrtcManager, isConnected]);
React.useEffect(() => {
const handleSecurityUpdate = (event) => {
setTimeout(() => {
setLastSecurityUpdate(0);
}, 100);
};
const handleRealSecurityCalculated = (event) => {
if (event.detail && event.detail.securityData) {
setRealSecurityLevel(event.detail.securityData);
setLastSecurityUpdate(Date.now());
}
};
document.addEventListener("security-level-updated", handleSecurityUpdate);
document.addEventListener("real-security-calculated", handleRealSecurityCalculated);
window.forceHeaderSecurityUpdate = (webrtcManager2) => {
if (webrtcManager2 && window.EnhancedSecureCryptoUtils) {
window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager2).then((securityData) => {
if (securityData && securityData.isRealData !== false) {
setRealSecurityLevel(securityData);
setLastSecurityUpdate(Date.now());
console.log("\u2705 Header security level force-updated");
}
}).catch((error) => {
console.error("\u274C Force update failed:", error);
});
} else {
setLastSecurityUpdate(0);
}
};
return () => {
document.removeEventListener("security-level-updated", handleSecurityUpdate);
document.removeEventListener("real-security-calculated", handleRealSecurityCalculated);
};
}, []);
React.useEffect(() => {
setHasActiveSession(true);
setCurrentTimeLeft(0);
setSessionType("premium");
}, []);
React.useEffect(() => {
setHasActiveSession(true);
setCurrentTimeLeft(0);
setSessionType("premium");
}, [sessionTimeLeft]);
React.useEffect(() => {
const handleForceUpdate = (event) => {
setHasActiveSession(true);
setCurrentTimeLeft(0);
setSessionType("premium");
};
const handleConnectionCleaned = () => {
if (window.DEBUG_MODE) {
console.log("\u{1F9F9} Connection cleaned - clearing security data in header");
}
setRealSecurityLevel(null);
setLastSecurityUpdate(0);
setHasActiveSession(false);
setCurrentTimeLeft(0);
setSessionType("unknown");
};
const handlePeerDisconnect = () => {
if (window.DEBUG_MODE) {
console.log("\u{1F44B} 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);
};
}, []);
const handleSecurityClick = async (event) => {
if (event && (event.button === 2 || event.ctrlKey || event.metaKey)) {
if (onDisconnect && typeof onDisconnect === "function") {
onDisconnect();
return;
}
}
event.preventDefault();
event.stopPropagation();
let realTestResults = null;
if (webrtcManager && window.EnhancedSecureCryptoUtils) {
try {
realTestResults = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(webrtcManager);
console.log("\u2705 Real security tests completed:", realTestResults);
} catch (error) {
console.error("\u274C Real security tests failed:", error);
}
} else {
console.log("\u26A0\uFE0F Cannot run security tests:", {
webrtcManager: !!webrtcManager,
cryptoUtils: !!window.EnhancedSecureCryptoUtils
});
}
if (!realTestResults && !realSecurityLevel) {
alert("Security verification in progress...\nPlease wait for real-time cryptographic verification to complete.");
return;
}
let securityData = realTestResults || realSecurityLevel;
if (!securityData) {
securityData = {
level: "UNKNOWN",
score: 0,
color: "gray",
verificationResults: {},
timestamp: Date.now(),
details: "Security verification not available",
isRealData: false,
passedChecks: 0,
totalChecks: 0
};
console.log("Using fallback security data:", securityData);
}
let message = `REAL-TIME SECURITY VERIFICATION
`;
message += `Security Level: ${securityData.level} (${securityData.score}%)
`;
message += `Verification Time: ${new Date(securityData.timestamp).toLocaleTimeString()}
`;
message += `Data Source: ${securityData.isRealData ? "Real Cryptographic Tests" : "Simulated Data"}
`;
if (securityData.verificationResults) {
message += "DETAILED CRYPTOGRAPHIC TESTS:\n";
message += "=" + "=".repeat(40) + "\n";
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";
passedTests.forEach(([key, result]) => {
const testName = key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
message += ` ${testName}: ${result.details || "Test passed"}
`;
});
message += "\n";
}
if (failedTests.length > 0) {
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 || "Test failed or unavailable"}
`;
});
message += "\n";
}
message += `SUMMARY:
`;
message += `Passed: ${securityData.passedChecks}/${securityData.totalChecks} tests
`;
message += `Score: ${securityData.score}/${securityData.maxPossibleScore || 100} points
`;
}
message += `SECURITY FEATURES STATUS:
`;
message += "=" + "=".repeat(40) + "\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
};
Object.entries(features).forEach(([feature, isEnabled]) => {
message += `${isEnabled ? "\u2705" : "\u274C"} ${feature}
`;
});
} else {
message += `\u2705 ECDSA Digital Signatures
`;
message += `\u2705 ECDH Key Exchange
`;
message += `\u2705 AES-GCM Encryption
`;
message += `\u2705 Message Integrity (HMAC)
`;
message += `\u2705 Perfect Forward Secrecy
`;
message += `\u2705 Replay Protection
`;
message += `\u2705 DTLS Fingerprint
`;
message += `\u2705 SAS Verification
`;
message += `\u2705 Metadata Protection
`;
message += `\u2705 Traffic Obfuscation
`;
}
message += `
${securityData.details || "Real cryptographic verification completed"}`;
if (securityData.isRealData) {
message += "\n\n\u2705 This is REAL-TIME verification using actual cryptographic functions.";
} else {
message += "\n\n\u26A0\uFE0F Warning: This data may be simulated. Connection may not be fully established.";
}
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);
modal.addEventListener("click", (e) => {
if (e.target === modal) {
document.body.removeChild(modal);
}
});
const handleKeyDown = (e) => {
if (e.key === "Escape") {
document.body.removeChild(modal);
document.removeEventListener("keydown", handleKeyDown);
}
};
document.addEventListener("keydown", handleKeyDown);
document.body.appendChild(modal);
};
const getStatusConfig = () => {
switch (status) {
case "connected":
return {
text: "Connected",
className: "status-connected",
badgeClass: "bg-green-500/10 text-green-400 border-green-500/20"
};
case "verifying":
return {
text: "Verifying...",
className: "status-verifying",
badgeClass: "bg-purple-500/10 text-purple-400 border-purple-500/20"
};
case "connecting":
return {
text: "Connecting...",
className: "status-connecting",
badgeClass: "bg-blue-500/10 text-blue-400 border-blue-500/20"
};
case "retrying":
return {
text: "Retrying...",
className: "status-connecting",
badgeClass: "bg-yellow-500/10 text-yellow-400 border-yellow-500/20"
};
case "failed":
return {
text: "Error",
className: "status-failed",
badgeClass: "bg-red-500/10 text-red-400 border-red-500/20"
};
case "reconnecting":
return {
text: "Reconnecting...",
className: "status-connecting",
badgeClass: "bg-yellow-500/10 text-yellow-400 border-yellow-500/20"
};
case "peer_disconnected":
return {
text: "Peer disconnected",
className: "status-failed",
badgeClass: "bg-orange-500/10 text-orange-400 border-orange-500/20"
};
default:
return {
text: "Not connected",
className: "status-disconnected",
badgeClass: "bg-gray-500/10 text-gray-400 border-gray-500/20"
};
}
};
const config = getStatusConfig();
const displaySecurityLevel = isConnected ? realSecurityLevel || securityLevel : null;
const getSecurityIndicatorDetails = () => {
if (!displaySecurityLevel) {
return {
tooltip: "Security verification in progress...",
isVerified: false,
dataSource: "loading"
};
}
const isRealData = displaySecurityLevel.isRealData !== false;
const baseTooltip = `${displaySecurityLevel.level} (${displaySecurityLevel.score}%)`;
if (isRealData) {
return {
tooltip: `${baseTooltip} - Real-time verification \u2705
Right-click or Ctrl+click to disconnect`,
isVerified: true,
dataSource: "real"
};
} else {
return {
tooltip: `${baseTooltip} - Estimated (connection establishing...)
Right-click or Ctrl+click to disconnect`,
isVerified: false,
dataSource: "estimated"
};
}
};
const securityDetails = getSecurityIndicatorDetails();
React.useEffect(() => {
window.debugHeaderSecurity = () => {
console.log("\u{1F50D} Header Security Debug:", {
realSecurityLevel,
lastSecurityUpdate,
isConnected,
webrtcManagerProp: !!webrtcManager,
windowWebrtcManager: !!window.webrtcManager,
cryptoUtils: !!window.EnhancedSecureCryptoUtils,
displaySecurityLevel,
securityDetails
});
};
return () => {
delete window.debugHeaderSecurity;
};
}, [realSecurityLevel, lastSecurityUpdate, isConnected, webrtcManager, displaySecurityLevel, securityDetails]);
return React.createElement("header", {
className: "header-minimal sticky top-0 z-50"
}, [
React.createElement("div", {
key: "container",
className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"
}, [
React.createElement("div", {
key: "content",
className: "flex items-center justify-between h-16"
}, [
// Logo and Title
React.createElement("div", {
key: "logo-section",
className: "flex items-center space-x-2 sm:space-x-3"
}, [
React.createElement("div", {
key: "logo",
className: "icon-container w-8 h-8 sm:w-10 sm:h-10"
}, [
React.createElement("i", {
className: "fas fa-shield-halved accent-orange text-sm sm:text-base"
})
]),
React.createElement("div", {
key: "title-section"
}, [
React.createElement("h1", {
key: "title",
className: "text-lg sm:text-xl font-semibold text-primary"
}, "SecureBit.chat"),
React.createElement("p", {
key: "subtitle",
className: "text-xs sm:text-sm text-muted hidden sm:block"
}, "End-to-end freedom v4.3.120")
])
]),
// Status and Controls - Responsive
React.createElement("div", {
key: "status-section",
className: "flex items-center space-x-2 sm:space-x-3"
}, [
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", {
key: "security-icon",
className: `w-6 h-6 rounded-full flex items-center justify-center relative ${displaySecurityLevel.color === "green" ? "bg-green-500/20" : displaySecurityLevel.color === "orange" ? "bg-orange-500/20" : displaySecurityLevel.color === "yellow" ? "bg-yellow-500/20" : "bg-red-500/20"} ${securityDetails.isVerified ? "" : "animate-pulse"}`
}, [
React.createElement("i", {
className: `fas fa-shield-alt text-xs ${displaySecurityLevel.color === "green" ? "text-green-400" : displaySecurityLevel.color === "orange" ? "text-orange-400" : displaySecurityLevel.color === "yellow" ? "text-yellow-400" : "text-red-400"}`
})
]),
React.createElement("div", {
key: "security-info",
className: "flex flex-col"
}, [
React.createElement("div", {
key: "security-level-text",
className: "text-xs font-medium text-primary flex items-center space-x-1"
}, [
React.createElement("span", {}, `${displaySecurityLevel.level} (${displaySecurityLevel.score}%)`)
]),
React.createElement(
"div",
{
key: "security-details",
className: "text-xs text-muted mt-1 hidden lg:block"
},
securityDetails.dataSource === "real" ? `${displaySecurityLevel.passedChecks || 0}/${displaySecurityLevel.totalChecks || 0} tests` : displaySecurityLevel.details || `Stage ${displaySecurityLevel.stage || 1}`
),
React.createElement("div", {
key: "security-progress",
className: "w-16 h-1 bg-gray-600 rounded-full overflow-hidden"
}, [
React.createElement("div", {
key: "progress-bar",
className: `h-full transition-all duration-500 ${displaySecurityLevel.color === "green" ? "bg-green-400" : displaySecurityLevel.color === "orange" ? "bg-orange-400" : displaySecurityLevel.color === "yellow" ? "bg-yellow-400" : "bg-red-400"}`,
style: { width: `${displaySecurityLevel.score}%` }
})
])
])
]),
// Mobile Security Indicator
displaySecurityLevel && React.createElement("div", {
key: "mobile-security",
className: "md:hidden flex items-center"
}, [
React.createElement("div", {
key: "mobile-security-icon",
className: `w-8 h-8 rounded-full flex items-center justify-center cursor-pointer hover:opacity-80 transition-opacity duration-200 relative ${displaySecurityLevel.color === "green" ? "bg-green-500/20" : displaySecurityLevel.color === "orange" ? "bg-orange-500/20" : displaySecurityLevel.color === "yellow" ? "bg-yellow-500/20" : "bg-red-500/20"} ${securityDetails.isVerified ? "" : "animate-pulse"}`,
title: securityDetails.tooltip,
onClick: handleSecurityClick,
onContextMenu: (e) => {
e.preventDefault();
if (onDisconnect && typeof onDisconnect === "function") {
onDisconnect();
}
}
}, [
React.createElement("i", {
className: `fas fa-shield-alt text-sm ${displaySecurityLevel.color === "green" ? "text-green-400" : displaySecurityLevel.color === "orange" ? "text-orange-400" : displaySecurityLevel.color === "yellow" ? "text-yellow-400" : "text-red-400"}`
})
])
]),
// Status Badge
React.createElement("div", {
key: "status-badge",
className: `px-2 sm:px-3 py-1.5 rounded-lg border ${config.badgeClass} flex items-center space-x-1 sm:space-x-2`
}, [
React.createElement("span", {
key: "status-dot",
className: `status-dot ${config.className}`
}),
React.createElement("span", {
key: "status-text",
className: "text-xs sm:text-sm font-medium"
}, config.text)
]),
// Disconnect Button
isConnected && React.createElement("button", {
key: "disconnect-btn",
onClick: onDisconnect,
className: "p-1.5 sm:px-3 sm:py-1.5 bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 rounded-lg transition-all duration-200 text-sm"
}, [
React.createElement("i", {
className: "fas fa-power-off sm:mr-2"
}),
React.createElement("span", {
className: "hidden sm:inline"
}, "Disconnect")
])
])
])
])
]);
};
window.EnhancedMinimalHeader = EnhancedMinimalHeader;
View File