fix: prevent encryption key loss and IndexedDB connection errors
- Disable timer-based key rotation for Double Ratchet mode - Auto-reinitialize encryption keys when missing but ECDH available - Preserve active keys during periodic cleanup in ratchet sessions - Fix IndexedDB "database closing" errors with connection checking - Add individual transactions per queue item to prevent race conditions
This commit is contained in:
1 parent
207e51361c
commit
79c8d08782
12 files changed
+315
-67
No files matched your search
@@ -1,4 +1,4 @@
|
||||
# SecureBit.chat v4.4.99
|
||||
# SecureBit.chat v4.5.22
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -31,22 +31,16 @@ SecureBit.chat is a revolutionary peer-to-peer messenger that prioritizes your p
|
||||
|
||||
---
|
||||
|
||||
## ✨ What's New in v4.4.99
|
||||
## ✨ What's New in v4.5.22
|
||||
|
||||
### 🔔 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
|
||||
### fix: prevent encryption key loss and IndexedDB connection errors
|
||||
|
||||
- Disable timer-based key rotation for Double Ratchet mode
|
||||
- Auto-reinitialize encryption keys when missing but ECDH available
|
||||
- Preserve active keys during periodic cleanup in ratchet sessions
|
||||
- Fix IndexedDB "database closing" errors with connection checking
|
||||
- Add individual transactions per queue item to prevent race conditions
|
||||
|
||||
### 🧹 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
|
||||
|
||||
### 🛡️ Security Enhancements
|
||||
- **ECDH + DTLS + SAS System** - Triple-layer security verification
|
||||
@@ -170,7 +164,7 @@ Modern browser with WebRTC support (Chrome 60+, Firefox 60+, Safari 12+), HTTPS
|
||||
|
||||
## 🗺️ Roadmap
|
||||
|
||||
**Current: v4.4.99** - Browser Notifications & Code Cleanup ✅
|
||||
**Current: v4.5.22** - Browser Notifications & Code Cleanup ✅
|
||||
|
||||
**Next Releases:**
|
||||
|
||||
@@ -336,7 +330,7 @@ MIT License - see **LICENSE** file for details.
|
||||
|
||||
---
|
||||
|
||||
**Latest Release: v4.4.99** - Browser Notifications & Code Cleanup
|
||||
**Latest Release: v4.5.22** - Browser Notifications & Code Cleanup
|
||||
|
||||
[🚀 Try Now](https://securebitchat.github.io/securebit-chat/) • [⭐ Star on GitHub](https://github.com/SecureBitChat/securebit-chat)
|
||||
|
||||
|
||||
Vendored
+53
-7
@@ -4724,7 +4724,7 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
|
||||
};
|
||||
this.onFileReceived = null;
|
||||
this.onFileError = null;
|
||||
this.keyRotationInterval = _EnhancedSecureWebRTCManager.TIMEOUTS.KEY_ROTATION_INTERVAL;
|
||||
this.keyRotationInterval = null;
|
||||
this.lastKeyRotation = Date.now();
|
||||
this.currentKeyVersion = 0;
|
||||
this.keyVersions = /* @__PURE__ */ new Map();
|
||||
@@ -4761,6 +4761,7 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
|
||||
packetPadding: this._config.packetPadding.enabled,
|
||||
antiFingerprinting: this._config.antiFingerprinting.enabled
|
||||
});
|
||||
this.sessionMode = "ratchet";
|
||||
this._hardenDebugModeReferences();
|
||||
this._initializeUnifiedScheduler();
|
||||
this._syncSecurityFeaturesWithTariff();
|
||||
@@ -5357,9 +5358,6 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
|
||||
if (this._keyStorageStats.activeKeys > 10) {
|
||||
this._secureLog("warn", "\u26A0\uFE0F High number of active keys detected. Consider rotation.");
|
||||
}
|
||||
if (Date.now() - (this._keyStorageStats.lastRotation || 0) > 36e5) {
|
||||
this._rotateKeys();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Send heartbeat message (called by unified scheduler)
|
||||
@@ -6791,7 +6789,12 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
|
||||
async _performPeriodicMemoryCleanup() {
|
||||
try {
|
||||
this._secureMemoryManager.isCleaning = true;
|
||||
this._secureCleanupCryptographicMaterials();
|
||||
const shouldPreserveActiveKeys = this.sessionMode === "ratchet" && this.isConnected && this.dataChannel && this.dataChannel.readyState === "open";
|
||||
if (shouldPreserveActiveKeys) {
|
||||
this._secureLog("debug", "\u{1F9F9} Skipping crypto key wipe during periodic cleanup (ratchet mode, active connection)");
|
||||
} else {
|
||||
this._secureCleanupCryptographicMaterials();
|
||||
}
|
||||
if (this.messageQueue && this.messageQueue.length > 100) {
|
||||
const excessMessages = this.messageQueue.splice(0, this.messageQueue.length - 50);
|
||||
excessMessages.forEach((message, index) => {
|
||||
@@ -7296,6 +7299,14 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
|
||||
}
|
||||
const normalizedReceived = receivedFingerprint.toLowerCase().replace(/:/g, "");
|
||||
const normalizedExpected = expectedFingerprint.toLowerCase().replace(/:/g, "");
|
||||
if (this.sessionMode === "ratchet" && normalizedExpected === normalizedReceived) {
|
||||
this._secureLog("info", "Same fingerprint detected \u2014 skip MITM warning (ratchet mode)", {
|
||||
context,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
this.isVerified = true;
|
||||
return true;
|
||||
}
|
||||
if (normalizedReceived !== normalizedExpected) {
|
||||
this._secureLog("error", "DTLS fingerprint mismatch - possible MITM attack", {
|
||||
context,
|
||||
@@ -7664,6 +7675,38 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
|
||||
}
|
||||
return hasAllKeys;
|
||||
}
|
||||
/**
|
||||
* Attempt to reinitialize encryption keys if missing
|
||||
* Uses existing ECDH key pair, peer public key, and session salt
|
||||
* Returns true if keys were (re)initialized successfully
|
||||
*/
|
||||
async _tryReinitializeEncryptionKeys() {
|
||||
try {
|
||||
if (this.encryptionKey && this.macKey && this.metadataKey) {
|
||||
return true;
|
||||
}
|
||||
const hasECDH = !!(this.ecdhKeyPair?.privateKey && (this.peerPublicKey || this.peerECDHPublicKey));
|
||||
const peerPublicKey = this.peerPublicKey || this.peerECDHPublicKey;
|
||||
if (!hasECDH || !peerPublicKey || !this.sessionSalt) {
|
||||
return false;
|
||||
}
|
||||
const derivedKeys = await window.EnhancedSecureCryptoUtils.deriveSharedKeys(
|
||||
this.ecdhKeyPair.privateKey,
|
||||
peerPublicKey,
|
||||
this.sessionSalt
|
||||
);
|
||||
await this._setEncryptionKeys(
|
||||
derivedKeys.messageKey,
|
||||
derivedKeys.macKey,
|
||||
derivedKeys.metadataKey,
|
||||
derivedKeys.fingerprint
|
||||
);
|
||||
return !!(this.encryptionKey && this.macKey && this.metadataKey);
|
||||
} catch (error) {
|
||||
this._secureLog("error", "Failed to reinitialize encryption keys", { error: error.message });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks whether a message is a file-transfer message
|
||||
* @param {string|object} data - message payload
|
||||
@@ -9261,6 +9304,9 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
|
||||
throw new Error("Data channel not ready");
|
||||
}
|
||||
try {
|
||||
if (!(this.encryptionKey && this.macKey && this.metadataKey)) {
|
||||
await this._tryReinitializeEncryptionKeys();
|
||||
}
|
||||
this._secureLog("debug", "sendMessage called", {
|
||||
hasDataChannel: !!this.dataChannel,
|
||||
dataChannelReady: this.dataChannel?.readyState === "open",
|
||||
@@ -15265,7 +15311,7 @@ Right-click or Ctrl+click to disconnect`,
|
||||
React.createElement("p", {
|
||||
key: "subtitle",
|
||||
className: "text-xs sm:text-sm text-muted hidden sm:block"
|
||||
}, "End-to-end freedom v4.4.99")
|
||||
}, "End-to-end freedom v4.5.22")
|
||||
])
|
||||
]),
|
||||
// Status and Controls - Responsive
|
||||
@@ -16020,7 +16066,7 @@ function Roadmap() {
|
||||
},
|
||||
// current and future phases
|
||||
{
|
||||
version: "v4.4.99",
|
||||
version: "v4.5.22",
|
||||
title: "Enhanced Security Edition",
|
||||
status: "current",
|
||||
date: "Now",
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": ["../src/notifications/SecureNotificationManager.js", "../src/notifications/NotificationIntegration.js", "../src/crypto/EnhancedSecureCryptoUtils.js", "../src/transfer/EnhancedSecureFileTransfer.js", "../src/network/EnhancedSecureWebRTCManager.js", "../src/scripts/app-boot.js", "../src/components/ui/Header.jsx", "../src/components/ui/DownloadApps.jsx", "../src/components/ui/UniqueFeatureSlider.jsx", "../src/components/ui/SecurityFeatures.jsx", "../src/components/ui/Testimonials.jsx", "../src/components/ui/ComparisonTable.jsx", "../src/components/ui/Roadmap.jsx", "../src/components/ui/FileTransfer.jsx"],
|
||||
"sourcesContent": ["/**\n * Secure and Reliable Notification Manager for P2P WebRTC Chat\n * Follows best practices: OWASP, MDN, Chrome DevRel\n * \n * @version 1.0.0\n * @author SecureBit Team\n * @license MIT\n */\n\nclass SecureChatNotificationManager {\n constructor(config = {}) {\n // Safely read Notification permission (iOS Safari may not define Notification)\n this.permission = (typeof Notification !== 'undefined' && Notification && typeof Notification.permission === 'string')\n ? Notification.permission\n : 'denied';\n this.isTabActive = this.checkTabActive(); // Initialize with proper check\n this.unreadCount = 0;\n this.originalTitle = document.title;\n this.notificationQueue = [];\n this.maxQueueSize = config.maxQueueSize || 5;\n this.rateLimitMs = config.rateLimitMs || 2000; // Spam protection\n this.lastNotificationTime = 0;\n this.trustedOrigins = config.trustedOrigins || [];\n \n // Secure context flag\n this.isSecureContext = window.isSecureContext;\n \n // Cross-browser compatibility for Page Visibility API\n this.hidden = this.getHiddenProperty();\n this.visibilityChange = this.getVisibilityChangeEvent();\n \n this.initVisibilityTracking();\n this.initSecurityChecks();\n }\n\n /**\n * Initialize security checks and validation\n * @private\n */\n initSecurityChecks() {\n // Security checks are performed silently\n }\n\n /**\n * Get hidden property name for cross-browser compatibility\n * @returns {string} Hidden property name\n * @private\n */\n getHiddenProperty() {\n if (typeof document.hidden !== \"undefined\") {\n return \"hidden\";\n } else if (typeof document.msHidden !== \"undefined\") {\n return \"msHidden\";\n } else if (typeof document.webkitHidden !== \"undefined\") {\n return \"webkitHidden\";\n }\n return \"hidden\"; // fallback\n }\n\n /**\n * Get visibility change event name for cross-browser compatibility\n * @returns {string} Visibility change event name\n * @private\n */\n getVisibilityChangeEvent() {\n if (typeof document.hidden !== \"undefined\") {\n return \"visibilitychange\";\n } else if (typeof document.msHidden !== \"undefined\") {\n return \"msvisibilitychange\";\n } else if (typeof document.webkitHidden !== \"undefined\") {\n return \"webkitvisibilitychange\";\n }\n return \"visibilitychange\"; // fallback\n }\n\n /**\n * Check if tab is currently active using multiple methods\n * @returns {boolean} True if tab is active\n * @private\n */\n checkTabActive() {\n // Primary method: Page Visibility API\n if (this.hidden && typeof document[this.hidden] !== \"undefined\") {\n return !document[this.hidden];\n }\n \n // Fallback method: document.hasFocus()\n if (typeof document.hasFocus === \"function\") {\n return document.hasFocus();\n }\n \n // Ultimate fallback: assume active\n return true;\n }\n\n /**\n * Initialize page visibility tracking (Page Visibility API)\n * @private\n */\n initVisibilityTracking() {\n // Primary method: Page Visibility API with cross-browser support\n if (typeof document.addEventListener !== \"undefined\" && typeof document[this.hidden] !== \"undefined\") {\n document.addEventListener(this.visibilityChange, () => {\n this.isTabActive = this.checkTabActive();\n \n if (this.isTabActive) {\n this.resetUnreadCount();\n this.clearNotificationQueue();\n }\n });\n }\n\n // Fallback method: Window focus/blur events\n window.addEventListener('focus', () => {\n this.isTabActive = this.checkTabActive();\n if (this.isTabActive) {\n this.resetUnreadCount();\n }\n });\n\n window.addEventListener('blur', () => {\n this.isTabActive = this.checkTabActive();\n });\n\n // Page unload cleanup\n window.addEventListener('beforeunload', () => {\n this.clearNotificationQueue();\n });\n }\n\n /**\n * Request notification permission (BEST PRACTICE: Only call in response to user action)\n * Never call on page load!\n * @returns {Promise<boolean>} Permission granted status\n */\n async requestPermission() {\n // Secure context check\n if (!this.isSecureContext || !('Notification' in window)) {\n return false;\n }\n\n if (this.permission === 'granted') {\n return true;\n }\n\n if (this.permission === 'denied') {\n return false;\n }\n\n try {\n this.permission = await Notification.requestPermission();\n return this.permission === 'granted';\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Update page title with unread count\n * @private\n */\n updateTitle() {\n if (this.unreadCount > 0) {\n document.title = `(${this.unreadCount}) ${this.originalTitle}`;\n } else {\n document.title = this.originalTitle;\n }\n }\n\n /**\n Line truncated
|
||||
"mappings": "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 truncated
|
||||
"sourcesContent": ["/**\n * Secure and Reliable Notification Manager for P2P WebRTC Chat\n * Follows best practices: OWASP, MDN, Chrome DevRel\n * \n * @version 1.0.0\n * @author SecureBit Team\n * @license MIT\n */\n\nclass SecureChatNotificationManager {\n constructor(config = {}) {\n // Safely read Notification permission (iOS Safari may not define Notification)\n this.permission = (typeof Notification !== 'undefined' && Notification && typeof Notification.permission === 'string')\n ? Notification.permission\n : 'denied';\n this.isTabActive = this.checkTabActive(); // Initialize with proper check\n this.unreadCount = 0;\n this.originalTitle = document.title;\n this.notificationQueue = [];\n this.maxQueueSize = config.maxQueueSize || 5;\n this.rateLimitMs = config.rateLimitMs || 2000; // Spam protection\n this.lastNotificationTime = 0;\n this.trustedOrigins = config.trustedOrigins || [];\n \n // Secure context flag\n this.isSecureContext = window.isSecureContext;\n \n // Cross-browser compatibility for Page Visibility API\n this.hidden = this.getHiddenProperty();\n this.visibilityChange = this.getVisibilityChangeEvent();\n \n this.initVisibilityTracking();\n this.initSecurityChecks();\n }\n\n /**\n * Initialize security checks and validation\n * @private\n */\n initSecurityChecks() {\n // Security checks are performed silently\n }\n\n /**\n * Get hidden property name for cross-browser compatibility\n * @returns {string} Hidden property name\n * @private\n */\n getHiddenProperty() {\n if (typeof document.hidden !== \"undefined\") {\n return \"hidden\";\n } else if (typeof document.msHidden !== \"undefined\") {\n return \"msHidden\";\n } else if (typeof document.webkitHidden !== \"undefined\") {\n return \"webkitHidden\";\n }\n return \"hidden\"; // fallback\n }\n\n /**\n * Get visibility change event name for cross-browser compatibility\n * @returns {string} Visibility change event name\n * @private\n */\n getVisibilityChangeEvent() {\n if (typeof document.hidden !== \"undefined\") {\n return \"visibilitychange\";\n } else if (typeof document.msHidden !== \"undefined\") {\n return \"msvisibilitychange\";\n } else if (typeof document.webkitHidden !== \"undefined\") {\n return \"webkitvisibilitychange\";\n }\n return \"visibilitychange\"; // fallback\n }\n\n /**\n * Check if tab is currently active using multiple methods\n * @returns {boolean} True if tab is active\n * @private\n */\n checkTabActive() {\n // Primary method: Page Visibility API\n if (this.hidden && typeof document[this.hidden] !== \"undefined\") {\n return !document[this.hidden];\n }\n \n // Fallback method: document.hasFocus()\n if (typeof document.hasFocus === \"function\") {\n return document.hasFocus();\n }\n \n // Ultimate fallback: assume active\n return true;\n }\n\n /**\n * Initialize page visibility tracking (Page Visibility API)\n * @private\n */\n initVisibilityTracking() {\n // Primary method: Page Visibility API with cross-browser support\n if (typeof document.addEventListener !== \"undefined\" && typeof document[this.hidden] !== \"undefined\") {\n document.addEventListener(this.visibilityChange, () => {\n this.isTabActive = this.checkTabActive();\n \n if (this.isTabActive) {\n this.resetUnreadCount();\n this.clearNotificationQueue();\n }\n });\n }\n\n // Fallback method: Window focus/blur events\n window.addEventListener('focus', () => {\n this.isTabActive = this.checkTabActive();\n if (this.isTabActive) {\n this.resetUnreadCount();\n }\n });\n\n window.addEventListener('blur', () => {\n this.isTabActive = this.checkTabActive();\n });\n\n // Page unload cleanup\n window.addEventListener('beforeunload', () => {\n this.clearNotificationQueue();\n });\n }\n\n /**\n * Request notification permission (BEST PRACTICE: Only call in response to user action)\n * Never call on page load!\n * @returns {Promise<boolean>} Permission granted status\n */\n async requestPermission() {\n // Secure context check\n if (!this.isSecureContext || !('Notification' in window)) {\n return false;\n }\n\n if (this.permission === 'granted') {\n return true;\n }\n\n if (this.permission === 'denied') {\n return false;\n }\n\n try {\n this.permission = await Notification.requestPermission();\n return this.permission === 'granted';\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Update page title with unread count\n * @private\n */\n updateTitle() {\n if (this.unreadCount > 0) {\n document.title = `(${this.unreadCount}) ${this.originalTitle}`;\n } else {\n document.title = this.originalTitle;\n }\n }\n\n /**\n Line truncated
|
||||
"mappings": "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 truncated
|
||||
"names": ["NotificationIntegration", "fileMessageTypes", "attempts", "start", "error", "attempts", "chunk", "fileMessageTypes", "window", "start", "webrtcManager", "start"]
|
||||
}
|
||||
Vendored
+1
-1
@@ -1688,7 +1688,7 @@ var EnhancedSecureP2PChat = () => {
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
handleMessage(" SecureBit.chat Enhanced Security Edition v4.4.99 - 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");
|
||||
handleMessage(" SecureBit.chat Enhanced Security Edition v4.5.22 - 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 (webrtcManagerRef2.current && webrtcManagerRef2.current.isConnected()) {
|
||||
|
||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": ["../src/app.jsx"],
|
||||
"sourcesContent": ["\r\n // Enhanced Copy Button with better UX\r\n const EnhancedCopyButton = ({ text, className = \"\", children }) => {\r\n const [copied, setCopied] = React.useState(false);\r\n \r\n const handleCopy = async () => {\r\n try {\r\n await navigator.clipboard.writeText(text);\r\n setCopied(true);\r\n setTimeout(() => setCopied(false), 2000);\r\n } catch (error) {\r\n console.error('Copy failed:', error);\r\n // Fallback for older browsers\r\n const textArea = document.createElement('textarea');\r\n textArea.value = text;\r\n document.body.appendChild(textArea);\r\n textArea.select();\r\n document.execCommand('copy');\r\n document.body.removeChild(textArea);\r\n setCopied(true);\r\n setTimeout(() => setCopied(false), 2000);\r\n }\r\n };\r\n \r\n return React.createElement('button', {\r\n onClick: handleCopy,\r\n className: `${className} transition-all duration-200`\r\n }, [\r\n React.createElement('i', {\r\n key: 'icon',\r\n className: `${copied ? 'fas fa-check accent-green' : 'fas fa-copy text-secondary'} mr-2`\r\n }),\r\n copied ? 'Copied!' : children\r\n ]);\r\n };\r\n \r\n // Verification Component\r\n const VerificationStep = ({ verificationCode, onConfirm, onReject, localConfirmed, remoteConfirmed, bothConfirmed }) => {\r\n return React.createElement('div', {\r\n className: \"card-minimal rounded-xl p-6 border-purple-500/20\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'header',\r\n className: \"flex items-center mb-4\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'icon',\r\n className: \"w-10 h-10 bg-purple-500/10 border border-purple-500/20 rounded-lg flex items-center justify-center mr-3\"\r\n }, [\r\n React.createElement('i', {\r\n className: 'fas fa-shield-alt accent-purple'\r\n })\r\n ]),\r\n React.createElement('h3', {\r\n key: 'title',\r\n className: \"text-lg font-medium text-primary\"\r\n }, \"Security verification\")\r\n ]),\r\n React.createElement('div', {\r\n key: 'content',\r\n className: \"space-y-4\"\r\n }, [\r\n React.createElement('p', {\r\n key: 'description',\r\n className: \"text-secondary text-sm\"\r\n }, \"Verify the security code with your contact via another communication channel (voice, SMS, etc.):\"),\r\n React.createElement('div', {\r\n key: 'code-display',\r\n className: \"text-center\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'code',\r\n className: \"verification-code text-2xl py-4\"\r\n }, verificationCode)\r\n ]),\r\n // Verification status indicators\r\n React.createElement('div', {\r\n key: 'verification-status',\r\n className: \"space-y-2\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'local-status',\r\n className: `flex items-center justify-between p-2 rounded-lg ${localConfirmed ? 'bg-green-500/10 border border-green-500/20' : 'bg-gray-500/10 border border-gray-500/20'}`\r\n }, [\r\n React.createElement('span', {\r\n Line truncated
|
||||
"sourcesContent": ["\r\n // Enhanced Copy Button with better UX\r\n const EnhancedCopyButton = ({ text, className = \"\", children }) => {\r\n const [copied, setCopied] = React.useState(false);\r\n \r\n const handleCopy = async () => {\r\n try {\r\n await navigator.clipboard.writeText(text);\r\n setCopied(true);\r\n setTimeout(() => setCopied(false), 2000);\r\n } catch (error) {\r\n console.error('Copy failed:', error);\r\n // Fallback for older browsers\r\n const textArea = document.createElement('textarea');\r\n textArea.value = text;\r\n document.body.appendChild(textArea);\r\n textArea.select();\r\n document.execCommand('copy');\r\n document.body.removeChild(textArea);\r\n setCopied(true);\r\n setTimeout(() => setCopied(false), 2000);\r\n }\r\n };\r\n \r\n return React.createElement('button', {\r\n onClick: handleCopy,\r\n className: `${className} transition-all duration-200`\r\n }, [\r\n React.createElement('i', {\r\n key: 'icon',\r\n className: `${copied ? 'fas fa-check accent-green' : 'fas fa-copy text-secondary'} mr-2`\r\n }),\r\n copied ? 'Copied!' : children\r\n ]);\r\n };\r\n \r\n // Verification Component\r\n const VerificationStep = ({ verificationCode, onConfirm, onReject, localConfirmed, remoteConfirmed, bothConfirmed }) => {\r\n return React.createElement('div', {\r\n className: \"card-minimal rounded-xl p-6 border-purple-500/20\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'header',\r\n className: \"flex items-center mb-4\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'icon',\r\n className: \"w-10 h-10 bg-purple-500/10 border border-purple-500/20 rounded-lg flex items-center justify-center mr-3\"\r\n }, [\r\n React.createElement('i', {\r\n className: 'fas fa-shield-alt accent-purple'\r\n })\r\n ]),\r\n React.createElement('h3', {\r\n key: 'title',\r\n className: \"text-lg font-medium text-primary\"\r\n }, \"Security verification\")\r\n ]),\r\n React.createElement('div', {\r\n key: 'content',\r\n className: \"space-y-4\"\r\n }, [\r\n React.createElement('p', {\r\n key: 'description',\r\n className: \"text-secondary text-sm\"\r\n }, \"Verify the security code with your contact via another communication channel (voice, SMS, etc.):\"),\r\n React.createElement('div', {\r\n key: 'code-display',\r\n className: \"text-center\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'code',\r\n className: \"verification-code text-2xl py-4\"\r\n }, verificationCode)\r\n ]),\r\n // Verification status indicators\r\n React.createElement('div', {\r\n key: 'verification-status',\r\n className: \"space-y-2\"\r\n }, [\r\n React.createElement('div', {\r\n key: 'local-status',\r\n className: `flex items-center justify-between p-2 rounded-lg ${localConfirmed ? 'bg-green-500/10 border border-green-500/20' : 'bg-gray-500/10 border border-gray-500/20'}`\r\n }, [\r\n React.createElement('span', {\r\n Line truncated
|
||||
"mappings": "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 truncated
|
||||
"names": ["setIsGeneratingKeys", "webrtcManagerRef", "offerData", "payload"]
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "SecureBit.chat v4.4.99 - ECDH + DTLS + SAS",
|
||||
"name": "SecureBit.chat v4.5.22 - ECDH + DTLS + SAS",
|
||||
"short_name": "SecureBit",
|
||||
"description": "P2P messenger with ECDH + DTLS + SAS security, military-grade cryptography and Lightning Network payments",
|
||||
"start_url": "./",
|
||||
|
||||
+1
-1
@@ -1924,7 +1924,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage(' SecureBit.chat Enhanced Security Edition v4.4.99 - 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');
|
||||
handleMessage(' SecureBit.chat Enhanced Security Edition v4.5.22 - 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) {
|
||||
|
||||
@@ -539,7 +539,7 @@ const EnhancedMinimalHeader = ({
|
||||
React.createElement('p', {
|
||||
key: 'subtitle',
|
||||
className: 'text-xs sm:text-sm text-muted hidden sm:block'
|
||||
}, 'End-to-end freedom v4.4.99')
|
||||
}, 'End-to-end freedom v4.5.22')
|
||||
])
|
||||
]),
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ function Roadmap() {
|
||||
|
||||
// current and future phases
|
||||
{
|
||||
version: "v4.4.99",
|
||||
version: "v4.5.22",
|
||||
title: "Enhanced Security Edition",
|
||||
status: "current",
|
||||
date: "Now",
|
||||
|
||||
@@ -330,7 +330,7 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
this.onFileError = null;
|
||||
|
||||
// PFS (Perfect Forward Secrecy) Implementation
|
||||
this.keyRotationInterval = EnhancedSecureWebRTCManager.TIMEOUTS.KEY_ROTATION_INTERVAL;
|
||||
this.keyRotationInterval = null; // отключаем таймерную ротацию
|
||||
this.lastKeyRotation = Date.now();
|
||||
this.currentKeyVersion = 0;
|
||||
this.keyVersions = new Map(); // Store key versions for PFS
|
||||
@@ -372,6 +372,9 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
antiFingerprinting: this._config.antiFingerprinting.enabled
|
||||
});
|
||||
|
||||
// Session mode: 'time' | 'ratchet' (Double Ratchet controls key lifecycle)
|
||||
this.sessionMode = 'ratchet';
|
||||
|
||||
// XSS Hardening - replace all window.DEBUG_MODE references
|
||||
this._hardenDebugModeReferences();
|
||||
|
||||
@@ -1183,9 +1186,6 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
this._secureLog('warn', '⚠️ High number of active keys detected. Consider rotation.');
|
||||
}
|
||||
|
||||
if (Date.now() - (this._keyStorageStats.lastRotation || 0) > 3600000) {
|
||||
this._rotateKeys();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2900,8 +2900,13 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
try {
|
||||
this._secureMemoryManager.isCleaning = true;
|
||||
|
||||
// Clean up any remaining sensitive data
|
||||
this._secureCleanupCryptographicMaterials();
|
||||
// Clean up sensitive data, but DO NOT wipe active crypto in ratchet session
|
||||
const shouldPreserveActiveKeys = (this.sessionMode === 'ratchet') && this.isConnected && this.dataChannel && this.dataChannel.readyState === 'open';
|
||||
if (shouldPreserveActiveKeys) {
|
||||
this._secureLog('debug', '🧹 Skipping crypto key wipe during periodic cleanup (ratchet mode, active connection)');
|
||||
} else {
|
||||
this._secureCleanupCryptographicMaterials();
|
||||
}
|
||||
|
||||
// Clean up message queue if it's too large
|
||||
if (this.messageQueue && this.messageQueue.length > 100) {
|
||||
@@ -3545,6 +3550,16 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
const normalizedReceived = receivedFingerprint.toLowerCase().replace(/:/g, '');
|
||||
const normalizedExpected = expectedFingerprint.toLowerCase().replace(/:/g, '');
|
||||
|
||||
// Ratchet mode: if fingerprint hasn't changed, treat as verified and skip warnings
|
||||
if (this.sessionMode === 'ratchet' && normalizedExpected === normalizedReceived) {
|
||||
this._secureLog('info', 'Same fingerprint detected — skip MITM warning (ratchet mode)', {
|
||||
context: context,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
this.isVerified = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (normalizedReceived !== normalizedExpected) {
|
||||
this._secureLog('error', 'DTLS fingerprint mismatch - possible MITM attack', {
|
||||
context: context,
|
||||
@@ -4014,6 +4029,46 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
return hasAllKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to reinitialize encryption keys if missing
|
||||
* Uses existing ECDH key pair, peer public key, and session salt
|
||||
* Returns true if keys were (re)initialized successfully
|
||||
*/
|
||||
async _tryReinitializeEncryptionKeys() {
|
||||
try {
|
||||
// If keys already present, nothing to do
|
||||
if (this.encryptionKey && this.macKey && this.metadataKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Require ECDH materials and session salt to derive keys
|
||||
const hasECDH = !!(this.ecdhKeyPair?.privateKey && (this.peerPublicKey || this.peerECDHPublicKey));
|
||||
const peerPublicKey = this.peerPublicKey || this.peerECDHPublicKey;
|
||||
if (!hasECDH || !peerPublicKey || !this.sessionSalt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Derive fresh keys
|
||||
const derivedKeys = await window.EnhancedSecureCryptoUtils.deriveSharedKeys(
|
||||
this.ecdhKeyPair.privateKey,
|
||||
peerPublicKey,
|
||||
this.sessionSalt
|
||||
);
|
||||
|
||||
await this._setEncryptionKeys(
|
||||
derivedKeys.messageKey,
|
||||
derivedKeys.macKey,
|
||||
derivedKeys.metadataKey,
|
||||
derivedKeys.fingerprint
|
||||
);
|
||||
|
||||
return !!(this.encryptionKey && this.macKey && this.metadataKey);
|
||||
} catch (error) {
|
||||
this._secureLog('error', 'Failed to reinitialize encryption keys', { error: error.message });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a message is a file-transfer message
|
||||
* @param {string|object} data - message payload
|
||||
@@ -6056,6 +6111,11 @@ async processOrderedPackets() {
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure encryption keys are available; try to reinitialize if needed
|
||||
if (!(this.encryptionKey && this.macKey && this.metadataKey)) {
|
||||
await this._tryReinitializeEncryptionKeys();
|
||||
}
|
||||
|
||||
this._secureLog('debug', 'sendMessage called', {
|
||||
hasDataChannel: !!this.dataChannel,
|
||||
dataChannelReady: this.dataChannel?.readyState === 'open',
|
||||
|
||||
+173
-25
@@ -95,6 +95,18 @@ class PWAOfflineManager {
|
||||
|
||||
request.onsuccess = () => {
|
||||
this.offlineDB = request.result;
|
||||
|
||||
// Listen for database close events
|
||||
this.offlineDB.onclose = () => {
|
||||
console.log('🔒 IndexedDB connection closed');
|
||||
this.offlineDB = null;
|
||||
};
|
||||
|
||||
// Listen for database errors
|
||||
this.offlineDB.onerror = (event) => {
|
||||
console.error('❌ IndexedDB error:', event);
|
||||
};
|
||||
|
||||
resolve(this.offlineDB);
|
||||
};
|
||||
|
||||
@@ -122,6 +134,37 @@ class PWAOfflineManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure database is open, reopen if necessary
|
||||
*/
|
||||
async ensureDatabaseOpen() {
|
||||
// Check if database exists and is open
|
||||
if (this.offlineDB && this.offlineDB.objectStoreNames.length > 0) {
|
||||
// Check if database connection is still valid
|
||||
try {
|
||||
// Try to access objectStoreNames to verify connection is active
|
||||
const storeNames = this.offlineDB.objectStoreNames;
|
||||
if (storeNames.length > 0) {
|
||||
return; // Database is open and valid
|
||||
}
|
||||
} catch (error) {
|
||||
// Database connection is invalid, need to reopen
|
||||
console.warn('⚠️ Database connection invalid, reopening...');
|
||||
this.offlineDB = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Database is closed or invalid, reopen it
|
||||
if (!this.offlineDB) {
|
||||
try {
|
||||
await this.initOfflineDB();
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to reopen database:', error);
|
||||
throw new Error('Database unavailable');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Network status changes
|
||||
window.addEventListener('online', () => {
|
||||
@@ -345,12 +388,6 @@ class PWAOfflineManager {
|
||||
}
|
||||
|
||||
async queueOfflineAction(action) {
|
||||
if (!this.offlineDB) {
|
||||
console.warn('⚠️ Offline database not available');
|
||||
this.offlineQueue.push(action);
|
||||
return;
|
||||
}
|
||||
|
||||
const queueItem = {
|
||||
...action,
|
||||
id: Date.now() + Math.random(),
|
||||
@@ -360,21 +397,32 @@ class PWAOfflineManager {
|
||||
maxRetries: action.maxRetries || 3
|
||||
};
|
||||
|
||||
// Always add to memory queue as fallback
|
||||
this.offlineQueue.push(queueItem);
|
||||
|
||||
if (!this.offlineDB) {
|
||||
console.warn('⚠️ Offline database not available, using memory queue only');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.ensureDatabaseOpen();
|
||||
|
||||
const transaction = this.offlineDB.transaction(['offlineQueue'], 'readwrite');
|
||||
const store = transaction.objectStore('offlineQueue');
|
||||
await this.promisifyRequest(store.add(queueItem));
|
||||
|
||||
this.offlineQueue.push(queueItem);
|
||||
|
||||
// Try to register background sync
|
||||
if (this.registration) {
|
||||
await this.registration.sync.register('offline-sync');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to queue offline action:', error);
|
||||
// Fallback to memory queue
|
||||
this.offlineQueue.push(queueItem);
|
||||
if (error.name === 'InvalidStateError' || error.message.includes('closing')) {
|
||||
console.warn('⚠️ Database was closing, item added to memory queue only');
|
||||
} else {
|
||||
console.error('❌ Failed to queue offline action:', error);
|
||||
}
|
||||
// Item already in memory queue, so no action needed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,9 +439,32 @@ class PWAOfflineManager {
|
||||
try {
|
||||
// Process database queue
|
||||
if (this.offlineDB) {
|
||||
const transaction = this.offlineDB.transaction(['offlineQueue'], 'readwrite');
|
||||
const store = transaction.objectStore('offlineQueue');
|
||||
const allItems = await this.promisifyRequest(store.getAll());
|
||||
// Ensure database is open before processing
|
||||
await this.ensureDatabaseOpen();
|
||||
|
||||
// Check if database is still open
|
||||
if (!this.offlineDB || this.offlineDB.objectStoreNames.length === 0) {
|
||||
console.warn('⚠️ Database not available, skipping queue processing');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all items first in a single transaction
|
||||
let allItems = [];
|
||||
try {
|
||||
const readTransaction = this.offlineDB.transaction(['offlineQueue'], 'readonly');
|
||||
const readStore = readTransaction.objectStore('offlineQueue');
|
||||
allItems = await this.promisifyRequest(readStore.getAll());
|
||||
} catch (error) {
|
||||
if (error.name === 'InvalidStateError' || error.message.includes('closing')) {
|
||||
console.warn('⚠️ Database was closing during read, retrying...');
|
||||
await this.ensureDatabaseOpen();
|
||||
const retryTransaction = this.offlineDB.transaction(['offlineQueue'], 'readonly');
|
||||
const retryStore = retryTransaction.objectStore('offlineQueue');
|
||||
allItems = await this.promisifyRequest(retryStore.getAll());
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by priority and timestamp
|
||||
allItems.sort((a, b) => {
|
||||
@@ -403,10 +474,17 @@ class PWAOfflineManager {
|
||||
return a.timestamp - b.timestamp; // Older first
|
||||
});
|
||||
|
||||
// Process each item with its own transaction to avoid "database closing" errors
|
||||
for (const item of allItems) {
|
||||
try {
|
||||
await this.processQueueItem(item);
|
||||
await this.promisifyRequest(store.delete(item.id));
|
||||
|
||||
// Create a new transaction for each delete operation
|
||||
await this.ensureDatabaseOpen();
|
||||
const deleteTransaction = this.offlineDB.transaction(['offlineQueue'], 'readwrite');
|
||||
const deleteStore = deleteTransaction.objectStore('offlineQueue');
|
||||
await this.promisifyRequest(deleteStore.delete(item.id));
|
||||
|
||||
processedCount++;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to process offline action:', error);
|
||||
@@ -417,11 +495,25 @@ class PWAOfflineManager {
|
||||
|
||||
if (item.retryCount >= item.maxRetries) {
|
||||
// Max retries reached, remove from queue
|
||||
await this.promisifyRequest(store.delete(item.id));
|
||||
console.log('❌ Max retries reached for action:', item.type);
|
||||
try {
|
||||
await this.ensureDatabaseOpen();
|
||||
const removeTransaction = this.offlineDB.transaction(['offlineQueue'], 'readwrite');
|
||||
const removeStore = removeTransaction.objectStore('offlineQueue');
|
||||
await this.promisifyRequest(removeStore.delete(item.id));
|
||||
console.log('❌ Max retries reached for action:', item.type);
|
||||
} catch (removeError) {
|
||||
console.error('❌ Failed to remove item after max retries:', removeError);
|
||||
}
|
||||
} else {
|
||||
// Update retry count in database
|
||||
await this.promisifyRequest(store.put(item));
|
||||
try {
|
||||
await this.ensureDatabaseOpen();
|
||||
const updateTransaction = this.offlineDB.transaction(['offlineQueue'], 'readwrite');
|
||||
const updateStore = updateTransaction.objectStore('offlineQueue');
|
||||
await this.promisifyRequest(updateStore.put(item));
|
||||
} catch (updateError) {
|
||||
console.error('❌ Failed to update retry count:', updateError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -640,6 +732,8 @@ class PWAOfflineManager {
|
||||
if (!this.offlineDB) return;
|
||||
|
||||
try {
|
||||
await this.ensureDatabaseOpen();
|
||||
|
||||
const appState = {
|
||||
component: 'app_state',
|
||||
timestamp: Date.now(),
|
||||
@@ -669,7 +763,11 @@ class PWAOfflineManager {
|
||||
await this.promisifyRequest(store.put(appState));
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to save application state:', error);
|
||||
if (error.name === 'InvalidStateError' || error.message.includes('closing')) {
|
||||
console.warn('⚠️ Database was closing, could not save application state');
|
||||
} else {
|
||||
console.error('❌ Failed to save application state:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,9 +793,20 @@ class PWAOfflineManager {
|
||||
throw new Error('Offline database not available');
|
||||
}
|
||||
|
||||
const transaction = this.offlineDB.transaction([storeName], 'readwrite');
|
||||
const store = transaction.objectStore(storeName);
|
||||
return this.promisifyRequest(store.put(data));
|
||||
try {
|
||||
await this.ensureDatabaseOpen();
|
||||
const transaction = this.offlineDB.transaction([storeName], 'readwrite');
|
||||
const store = transaction.objectStore(storeName);
|
||||
return await this.promisifyRequest(store.put(data));
|
||||
} catch (error) {
|
||||
if (error.name === 'InvalidStateError' || error.message.includes('closing')) {
|
||||
await this.ensureDatabaseOpen();
|
||||
const retryTransaction = this.offlineDB.transaction([storeName], 'readwrite');
|
||||
const retryStore = retryTransaction.objectStore(storeName);
|
||||
return await this.promisifyRequest(retryStore.put(data));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getStoredData(storeName, key) {
|
||||
@@ -706,11 +815,24 @@ class PWAOfflineManager {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.ensureDatabaseOpen();
|
||||
const transaction = this.offlineDB.transaction([storeName], 'readonly');
|
||||
const store = transaction.objectStore(storeName);
|
||||
const result = await this.promisifyRequest(store.get(key));
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (error.name === 'InvalidStateError' || error.message.includes('closing')) {
|
||||
console.warn(`⚠️ Database was closing during get from ${storeName}, retrying...`);
|
||||
try {
|
||||
await this.ensureDatabaseOpen();
|
||||
const retryTransaction = this.offlineDB.transaction([storeName], 'readonly');
|
||||
const retryStore = retryTransaction.objectStore(storeName);
|
||||
return await this.promisifyRequest(retryStore.get(key));
|
||||
} catch (retryError) {
|
||||
console.error(`❌ Failed to get stored data from ${storeName} after retry:`, retryError);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
console.error(`❌ Failed to get stored data from ${storeName}:`, error);
|
||||
return null;
|
||||
}
|
||||
@@ -720,6 +842,7 @@ class PWAOfflineManager {
|
||||
if (!this.offlineDB) return;
|
||||
|
||||
try {
|
||||
await this.ensureDatabaseOpen();
|
||||
const transaction = this.offlineDB.transaction([storeName], 'readwrite');
|
||||
const store = transaction.objectStore(storeName);
|
||||
|
||||
@@ -730,7 +853,11 @@ class PWAOfflineManager {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to clear stored data from ${storeName}:`, error);
|
||||
if (error.name === 'InvalidStateError' || error.message.includes('closing')) {
|
||||
console.warn(`⚠️ Database was closing during clear from ${storeName}`);
|
||||
} else {
|
||||
console.error(`❌ Failed to clear stored data from ${storeName}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,6 +885,8 @@ class PWAOfflineManager {
|
||||
const cutoffTime = Date.now() - maxAge;
|
||||
|
||||
try {
|
||||
await this.ensureDatabaseOpen();
|
||||
|
||||
const transaction = this.offlineDB.transaction(['offlineQueue', 'messageQueue'], 'readwrite');
|
||||
|
||||
// Clean offline queue
|
||||
@@ -790,7 +919,11 @@ class PWAOfflineManager {
|
||||
|
||||
console.log('🧹 Old offline data cleaned up');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to cleanup old data:', error);
|
||||
if (error.name === 'InvalidStateError' || error.message.includes('closing')) {
|
||||
console.warn('⚠️ Database was closing during cleanup, skipping...');
|
||||
} else {
|
||||
console.error('❌ Failed to cleanup old data:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1030,10 +1163,25 @@ class PWAOfflineManager {
|
||||
destroy() {
|
||||
if (this.reconnectInterval) {
|
||||
clearInterval(this.reconnectInterval);
|
||||
this.reconnectInterval = null;
|
||||
}
|
||||
|
||||
// Set sync flag to prevent new operations
|
||||
this.syncInProgress = true;
|
||||
|
||||
// Close database connection
|
||||
if (this.offlineDB) {
|
||||
this.offlineDB.close();
|
||||
try {
|
||||
// Only close if database is not in a transaction
|
||||
// IndexedDB will automatically close when all transactions complete
|
||||
if (this.offlineDB.objectStoreNames.length > 0) {
|
||||
this.offlineDB.close();
|
||||
}
|
||||
this.offlineDB = null;
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Error closing database:', error);
|
||||
this.offlineDB = null;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('🧹 Offline Manager destroyed');
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// SecureBit.chat Service Worker
|
||||
// Conservative PWA Edition v4.4.99 - Minimal Caching Strategy
|
||||
// Conservative PWA Edition v4.5.22 - Minimal Caching Strategy
|
||||
|
||||
const CACHE_NAME = 'securebit-pwa-v4.4.99';
|
||||
const STATIC_CACHE = 'securebit-pwa-static-v4.4.99';
|
||||
const DYNAMIC_CACHE = 'securebit-pwa-dynamic-v4.4.99';
|
||||
const CACHE_NAME = 'securebit-pwa-v4.5.22';
|
||||
const STATIC_CACHE = 'securebit-pwa-static-v4.5.22';
|
||||
const DYNAMIC_CACHE = 'securebit-pwa-dynamic-v4.5.22';
|
||||
|
||||
// Essential files for PWA offline functionality
|
||||
const STATIC_ASSETS = [
|
||||
|
||||
Reference in new issue
Block a user