Compare commits
39 Commits
ea8cf47118
...
3c2bac588c
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c2bac588c | |||
| 2ae65eb749 | |||
| 4a3249b2a6 | |||
| 456545f055 | |||
| 40381cc0a1 | |||
| 0d7835cfa2 | |||
| 7604381584 | |||
| a7a5861e0a | |||
| 63a19e6a4c | |||
| b18e943abd | |||
| 9c1258cd1e | |||
| 60e4bb6b8a | |||
| 4233ba3d7e | |||
| 333c4b87b5 | |||
| 5096a09811 | |||
| 5c658cdd6c | |||
| e209d3e6f5 | |||
| 49e5a054cb | |||
| 9d0372484a | |||
| 69fa5ffc28 | |||
| ed169b4c8c | |||
| 906562333e | |||
| 4e7f5867b5 | |||
| 5ddfd1f5b3 | |||
| 1acbc12a92 | |||
| 3ed8766fc9 | |||
| b087adfecc | |||
| 5b5cc67fdc | |||
| 93e4b7ad19 | |||
| fce18d27a5 | |||
| 03e06f59dc | |||
| 5df6cb0d16 | |||
| 3730f46a08 | |||
| abd6f9a7f5 | |||
| 99469e5bd5 | |||
| 2306b32482 | |||
| cbb6a8fa31 | |||
| 4be6fc55f5 | |||
| cb62d8eb3b |
@@ -0,0 +1,32 @@
|
||||
name: "CodeQL Analysis"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
- cron: '0 3 * * *'
|
||||
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze CodeQL
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: javascript,typescript
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
@@ -0,0 +1,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
|
||||
@@ -1,185 +1,99 @@
|
||||
# SecureBit.chat v4.3.120 - UX/UI Redesign + Binary QR System
|
||||
# SecureBit.chat v4.4.18
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
**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**
|
||||
|
||||
[](https://github.com/SecureBitChat/securebit-chat/releases/latest)
|
||||
[](https://securebitchat.github.io/securebit-chat/)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[]()
|
||||
|
||||
[🚀 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** | 🏆 Non‑extractable + hardware | ✅ Secure storage | ✅ Local storage | ✅ Secure storage |
|
||||
| **Perfect Forward Secrecy** | 🏆 Auto rotation (5 min) | ✅ Double Ratchet | ⚠️ Partial (groups) | ✅ Session Ratchet |
|
||||
| **Open Source** | 🏆 100% + auditable | ✅ Fully open | ⚠️ Only clients | ✅ Fully open |
|
||||
| **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 military‑grade 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: Self‑Host
|
||||
### 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 800‑56A — ECDH Key Agreement
|
||||
* NIST SP 800‑186 — 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
|
||||
* Cross‑device synchronization
|
||||
* Enhanced PWA features
|
||||
|
||||
### v5.0 (Q4 2025) — Quantum‑Resistant Edition
|
||||
|
||||
* CRYSTALS‑Kyber post‑quantum key exchange
|
||||
* SPHINCS+ post‑quantum signatures
|
||||
* Hybrid classical + post‑quantum schemes
|
||||
* Quantum‑safe migration path
|
||||
|
||||
### v5.5 (Q2 2026) — Group Communications
|
||||
|
||||
* P2P group chats (up to 8 participants)
|
||||
* Mesh networking topology
|
||||
|
||||
* Anonymous group administration
|
||||
* Group file sharing
|
||||
|
||||
### v6.0 (2027) — Decentralized Network
|
||||
|
||||
* DHT‑based peer discovery
|
||||
* Built‑in 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 client‑side
|
||||
# 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
|
||||
* 🔄 Post‑quantum 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
|
||||
* Out‑of‑band verification — Prevents man‑in‑the‑middle attacks
|
||||
* Traffic obfuscation — Defeats network analysis
|
||||
* Memory protection — No persistent storage of sensitive data
|
||||
* Session isolation — Complete cleanup between sessions
|
||||
* 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
|
||||
* Community‑driven — 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 security‑first 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>
|
||||
@@ -0,0 +1,20 @@
|
||||
# Notification Sound Asset
|
||||
#
|
||||
# This file should contain a short, pleasant notification sound in MP3 format.
|
||||
# Recommended specifications:
|
||||
# - Duration: 1-2 seconds
|
||||
# - Format: MP3, 44.1kHz, 128kbps
|
||||
# - Volume: Moderate (not too loud)
|
||||
# - License: Ensure proper licensing for commercial use
|
||||
#
|
||||
# You can create this using:
|
||||
# 1. Text-to-speech generators
|
||||
# 2. Audio editing software
|
||||
# 3. Free notification sound libraries
|
||||
# 4. AI-generated sounds
|
||||
#
|
||||
# Example sources:
|
||||
# - Freesound.org (CC licensed)
|
||||
# - Zapsplat.com
|
||||
# - Adobe Audition
|
||||
# - Audacity (free)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+828
-1004
File diff suppressed because it is too large
Load Diff
Vendored
+4
-4
File diff suppressed because one or more lines are too long
Vendored
+279
-353
@@ -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() {
|
||||
|
||||
Vendored
+3
-3
File diff suppressed because one or more lines are too long
+10
-9
@@ -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
@@ -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
@@ -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'));
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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')
|
||||
])
|
||||
]),
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.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;
|
||||
}
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
Vendored
+25
-24
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
Reference in New Issue
Block a user