// PWA Offline Manager for SecureBit.chat
// Enhanced Security Edition v4.02.442
// Handles offline functionality, data synchronization, and user experience
class PWAOfflineManager {
constructor() {
this.isOnline = navigator.onLine;
this.offlineDB = null;
this.offlineQueue = [];
this.syncInProgress = false;
this.lastSyncTime = null;
this.offlineIndicator = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectInterval = null;
// Offline storage configuration
this.dbConfig = {
name: 'SecureBitOffline',
version: 2,
stores: {
offlineQueue: {
keyPath: 'id',
autoIncrement: true,
indexes: {
timestamp: { unique: false },
type: { unique: false },
priority: { unique: false }
}
},
sessionData: {
keyPath: 'key'
},
messageQueue: {
keyPath: 'id',
autoIncrement: true,
indexes: {
timestamp: { unique: false },
channelId: { unique: false }
}
},
appState: {
keyPath: 'component'
}
}
};
this.init();
}
async init() {
try {
// Initialize offline database
await this.initOfflineDB();
// Setup event listeners
this.setupEventListeners();
// Create offline indicator
this.createOfflineIndicator();
// Register background sync
this.registerBackgroundSync();
// Setup periodic cleanup
this.setupPeriodicCleanup();
// Show initial connection status
this.updateConnectionStatus(this.isOnline);
// Try to process any pending queue items
if (this.isOnline) {
await this.processOfflineQueue();
}
} catch (error) {
console.error('โ Offline Manager initialization failed:', error);
this.handleInitializationError(error);
}
}
async initOfflineDB() {
if (!('indexedDB' in window)) {
throw new Error('IndexedDB not supported');
}
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbConfig.name, this.dbConfig.version);
request.onerror = () => {
reject(new Error('Failed to open offline database'));
};
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);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create object stores
Object.entries(this.dbConfig.stores).forEach(([storeName, config]) => {
if (!db.objectStoreNames.contains(storeName)) {
const store = db.createObjectStore(storeName, {
keyPath: config.keyPath,
autoIncrement: config.autoIncrement || false
});
// Create indexes
if (config.indexes) {
Object.entries(config.indexes).forEach(([indexName, indexConfig]) => {
store.createIndex(indexName, indexName, indexConfig);
});
}
}
});
};
});
}
/**
* 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', () => {
this.isOnline = true;
this.reconnectAttempts = 0;
this.updateConnectionStatus(true);
this.handleConnectionRestored();
});
window.addEventListener('offline', () => {
this.isOnline = false;
this.updateConnectionStatus(false);
this.handleConnectionLost();
});
// App visibility changes
document.addEventListener('visibilitychange', () => {
if (!document.hidden && this.isOnline) {
// Try to sync when app becomes visible
setTimeout(() => this.processOfflineQueue(), 1000);
}
});
// Listen for WebRTC connection events
document.addEventListener('peer-disconnect', (event) => {
if (!this.isOnline) {
this.handleOfflineDisconnection(event.detail);
}
});
// Listen for failed network requests
window.addEventListener('unhandledrejection', (event) => {
if (this.isNetworkError(event.reason)) {
this.handleNetworkFailure(event.reason);
}
});
// Listen for beforeunload to save state
window.addEventListener('beforeunload', () => {
this.saveApplicationState();
});
}
createOfflineIndicator() {
this.offlineIndicator = document.createElement('div');
this.offlineIndicator.id = 'pwa-connection-status';
this.offlineIndicator.className = 'hidden fixed top-4 left-1/2 transform -translate-x-1/2 z-50 transition-all duration-300';
document.body.appendChild(this.offlineIndicator);
}
updateConnectionStatus(isOnline) {
if (!this.offlineIndicator) return;
if (isOnline) {
this.offlineIndicator.innerHTML = `
`;
this.offlineIndicator.classList.remove('hidden');
// Hide after 3 seconds
setTimeout(() => {
this.offlineIndicator.classList.add('hidden');
}, 3000);
} else {
this.offlineIndicator.innerHTML = `
`;
this.offlineIndicator.classList.remove('hidden');
}
}
async handleConnectionRestored() {
console.log('๐ Handling connection restoration...');
try {
// Process offline queue
await this.processOfflineQueue();
// Restore WebRTC connections if needed
await this.attemptWebRTCReconnection();
// Show success notification
this.showReconnectionSuccess();
} catch (error) {
console.error('โ Connection restoration failed:', error);
this.showReconnectionError(error);
}
}
handleConnectionLost() {
console.log('๐ด Handling connection loss...');
// Show offline guidance
this.showOfflineGuidance();
// Save current application state
this.saveApplicationState();
// Start reconnection attempts
this.startReconnectionAttempts();
}
showOfflineGuidance() {
// Don't show if already shown recently
const lastShown = localStorage.getItem('offline_guidance_shown');
if (lastShown && Date.now() - parseInt(lastShown) < 60000) { // 1 minute
return;
}
const guidance = document.createElement('div');
guidance.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 backdrop-blur-sm';
guidance.innerHTML = `
Connection Lost
SecureBit.chat is now in offline mode. Some features are limited, but your data is safe.
Your session and keys are preserved
No data is stored on servers
Messages will sync when online
`;
document.body.appendChild(guidance);
// Save that we showed the guidance
localStorage.setItem('offline_guidance_shown', Date.now().toString());
// Auto-remove after 15 seconds
setTimeout(() => {
if (guidance.parentElement) {
guidance.remove();
}
}, 15000);
}
startReconnectionAttempts() {
if (this.reconnectInterval) {
clearInterval(this.reconnectInterval);
}
this.reconnectInterval = setInterval(() => {
if (this.isOnline) {
clearInterval(this.reconnectInterval);
this.reconnectInterval = null;
return;
}
this.reconnectAttempts++;
// Try to detect if we're actually back online
this.checkOnlineStatus();
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
clearInterval(this.reconnectInterval);
this.reconnectInterval = null;
}
}, 10000); // Try every 10 seconds
}
async checkOnlineStatus() {
try {
// Try to fetch a small resource to check connectivity
const response = await fetch('/favicon.ico', {
method: 'HEAD',
cache: 'no-cache',
signal: AbortSignal.timeout(5000)
});
if (response.ok && !this.isOnline) {
this.isOnline = true;
this.handleConnectionRestored();
}
} catch (error) {
// Still offline
console.log('๐ด Still offline');
}
}
async queueOfflineAction(action) {
const queueItem = {
...action,
id: Date.now() + Math.random(),
timestamp: Date.now(),
priority: action.priority || 1,
retryCount: 0,
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));
// Try to register background sync
if (this.registration) {
await this.registration.sync.register('offline-sync');
}
} catch (error) {
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
}
}
async processOfflineQueue() {
if (this.syncInProgress || !this.isOnline) {
return;
}
this.syncInProgress = true;
let processedCount = 0;
let errorCount = 0;
try {
// Process database queue
if (this.offlineDB) {
// 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) => {
if (a.priority !== b.priority) {
return b.priority - a.priority; // Higher priority first
}
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);
// 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);
errorCount++;
// Increment retry count
item.retryCount = (item.retryCount || 0) + 1;
if (item.retryCount >= item.maxRetries) {
// Max retries reached, remove from queue
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
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);
}
}
}
}
}
// Process in-memory queue as fallback
const memoryQueue = [...this.offlineQueue];
this.offlineQueue = [];
for (const item of memoryQueue) {
try {
await this.processQueueItem(item);
processedCount++;
} catch (error) {
console.error('โ Failed to process memory queue item:', error);
errorCount++;
item.retryCount = (item.retryCount || 0) + 1;
if (item.retryCount < item.maxRetries) {
this.offlineQueue.push(item); // Re-queue for retry
}
}
}
this.lastSyncTime = Date.now();
if (processedCount > 0 || errorCount > 0) {
this.showSyncNotification(processedCount, errorCount);
}
} catch (error) {
console.error('โ Error processing offline queue:', error);
} finally {
this.syncInProgress = false;
}
}
async processQueueItem(item) {
switch (item.type) {
case 'message':
return this.retryMessage(item.data);
case 'connection':
return this.retryConnection(item.data);
case 'payment_check':
return this.retryPaymentCheck(item.data);
case 'session_verification':
return this.retrySessionVerification(item.data);
case 'key_exchange':
return this.retryKeyExchange(item.data);
default:
console.warn('Unknown queue item type:', item.type);
throw new Error(`Unknown queue item type: ${item.type}`);
}
}
async retryMessage(messageData) {
// Retry sending message when back online
if (window.webrtcManager && window.webrtcManager.isConnected()) {
return window.webrtcManager.sendMessage(messageData.content);
}
throw new Error('WebRTC not connected');
}
async retryConnection(connectionData) {
// Retry connection establishment
if (window.webrtcManager) {
return window.webrtcManager.retryConnection();
}
throw new Error('WebRTC manager not available');
}
async retryPaymentCheck(paymentData) {
// Retry payment verification
if (window.sessionManager) {
return window.sessionManager.checkPaymentStatus(paymentData.checkingId);
}
throw new Error('Session manager not available');
}
async retrySessionVerification(sessionData) {
// Retry session verification
if (window.sessionManager) {
return window.sessionManager.verifyPayment(sessionData.preimage, sessionData.paymentHash);
}
throw new Error('Session manager not available');
}
async retryKeyExchange(keyData) {
// Retry key exchange
if (window.webrtcManager) {
return window.webrtcManager.handleKeyExchange(keyData);
}
throw new Error('WebRTC manager not available');
}
showSyncNotification(successCount, errorCount) {
const notification = document.createElement('div');
notification.className = 'fixed bottom-4 right-4 bg-green-500 text-white p-4 rounded-lg shadow-lg z-50 max-w-sm transform translate-x-full transition-transform duration-300';
let message = '';
if (successCount > 0 && errorCount === 0) {
message = `โ
Synced ${successCount} offline action${successCount > 1 ? 's' : ''}`;
} else if (successCount > 0 && errorCount > 0) {
message = `โ ๏ธ Synced ${successCount}, ${errorCount} failed`;
} else if (errorCount > 0) {
message = `โ ${errorCount} sync error${errorCount > 1 ? 's' : ''}`;
}
notification.innerHTML = `
`;
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
// Auto-remove after 4 seconds
setTimeout(() => {
notification.classList.add('translate-x-full');
setTimeout(() => notification.remove(), 300);
}, 4000);
}
async attemptWebRTCReconnection() {
if (!window.webrtcManager) return;
try {
// Check if we had an active connection before going offline
const savedConnectionState = await this.getStoredData('sessionData', 'connection_state');
if (savedConnectionState && savedConnectionState.wasConnected) {
// Show reconnection indicator
this.showReconnectionIndicator();
// Attempt to restore connection
// This would depend on your specific WebRTC implementation
if (window.webrtcManager.attemptReconnection) {
await window.webrtcManager.attemptReconnection(savedConnectionState.data);
}
}
} catch (error) {
console.error('โ WebRTC reconnection failed:', error);
}
}
showReconnectionIndicator() {
const indicator = document.createElement('div');
indicator.className = 'fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-blue-500/90 text-white px-6 py-4 rounded-lg backdrop-blur-sm z-50';
indicator.innerHTML = `
Reconnecting...
Restoring secure connection
`;
document.body.appendChild(indicator);
// Remove after 5 seconds or when connection is restored
setTimeout(() => {
if (indicator.parentElement) {
indicator.remove();
}
}, 5000);
}
showReconnectionSuccess() {
const notification = document.createElement('div');
notification.className = 'fixed top-4 right-4 bg-green-500 text-white p-4 rounded-lg shadow-lg z-50 max-w-sm';
notification.innerHTML = `
Reconnected!
All services restored
`;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
}
showReconnectionError(error) {
const notification = document.createElement('div');
notification.className = 'fixed top-4 right-4 bg-yellow-500 text-black p-4 rounded-lg shadow-lg z-50 max-w-sm';
notification.innerHTML = `
Reconnection Issue
Some features may need manual restart
`;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 5000);
}
async saveApplicationState() {
if (!this.offlineDB) return;
try {
await this.ensureDatabaseOpen();
const appState = {
component: 'app_state',
timestamp: Date.now(),
url: window.location.href,
// Save WebRTC connection state
webrtc: window.webrtcManager ? {
isConnected: window.webrtcManager.isConnected(),
connectionState: window.webrtcManager.getConnectionInfo(),
} : null,
// Save session state
session: window.sessionManager ? {
hasActiveSession: window.sessionManager.hasActiveSession(),
sessionInfo: window.sessionManager.getSessionInfo(),
} : null,
// Save UI state
ui: {
currentTab: document.querySelector('.active')?.id,
scrollPosition: window.pageYOffset,
}
};
const transaction = this.offlineDB.transaction(['appState'], 'readwrite');
const store = transaction.objectStore('appState');
await this.promisifyRequest(store.put(appState));
} catch (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);
}
}
}
async restoreApplicationState() {
if (!this.offlineDB) return null;
try {
const savedState = await this.getStoredData('appState', 'app_state');
if (savedState && Date.now() - savedState.timestamp < 24 * 60 * 60 * 1000) { // 24 hours
console.log('๐ Restoring application state from offline storage');
return savedState;
}
} catch (error) {
console.error('โ Failed to restore application state:', error);
}
return null;
}
async storeData(storeName, data) {
if (!this.offlineDB) {
throw new Error('Offline database not available');
}
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) {
if (!this.offlineDB) {
return null;
}
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;
}
}
async clearStoredData(storeName, key = null) {
if (!this.offlineDB) return;
try {
await this.ensureDatabaseOpen();
const transaction = this.offlineDB.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
if (key) {
await this.promisifyRequest(store.delete(key));
} else {
await this.promisifyRequest(store.clear());
}
} catch (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);
}
}
}
registerBackgroundSync() {
if ('serviceWorker' in navigator && 'sync' in window.ServiceWorkerRegistration.prototype) {
navigator.serviceWorker.ready.then(registration => {
this.registration = registration;
});
} else {
console.warn('โ ๏ธ Background sync not supported');
}
}
setupPeriodicCleanup() {
// Clean up old data every hour
setInterval(() => {
this.cleanupOldData();
}, 60 * 60 * 1000);
}
async cleanupOldData() {
if (!this.offlineDB) return;
const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
const cutoffTime = Date.now() - maxAge;
try {
await this.ensureDatabaseOpen();
const transaction = this.offlineDB.transaction(['offlineQueue', 'messageQueue'], 'readwrite');
// Clean offline queue
const queueStore = transaction.objectStore('offlineQueue');
const queueIndex = queueStore.index('timestamp');
const queueRange = IDBKeyRange.upperBound(cutoffTime);
const queueRequest = queueIndex.openCursor(queueRange);
queueRequest.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
};
// Clean message queue
const messageStore = transaction.objectStore('messageQueue');
const messageIndex = messageStore.index('timestamp');
const messageRange = IDBKeyRange.upperBound(cutoffTime);
const messageRequest = messageIndex.openCursor(messageRange);
messageRequest.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
};
console.log('๐งน Old offline data cleaned up');
} catch (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);
}
}
}
handleOfflineDisconnection(details) {
// Save connection state for recovery
this.storeData('sessionData', {
key: 'connection_state',
wasConnected: true,
disconnectReason: details.reason,
timestamp: Date.now(),
data: details
});
// Show user feedback
this.showOfflineDisconnectionNotice();
}
showOfflineDisconnectionNotice() {
const notice = document.createElement('div');
notice.className = 'fixed bottom-4 left-4 right-4 bg-yellow-500/90 text-black p-4 rounded-lg backdrop-blur-sm z-50';
notice.innerHTML = `
Connection Interrupted
Your secure connection was lost due to network issues.
It will be restored automatically when you're back online.
`;
document.body.appendChild(notice);
setTimeout(() => {
if (notice.parentElement) {
notice.remove();
}
}, 8000);
}
handleNetworkFailure(error) {
// Queue the failed action for retry if appropriate
if (this.shouldQueueFailedRequest(error)) {
this.queueOfflineAction({
type: 'network_retry',
data: { error: error?.message },
priority: 1,
maxRetries: 2
});
}
}
shouldQueueFailedRequest(error) {
if (!error) return false;
const queueableErrors = [
'fetch',
'network',
'connection',
'timeout',
'offline',
'ERR_NETWORK',
'ERR_INTERNET_DISCONNECTED'
];
const errorString = error.toString().toLowerCase();
return queueableErrors.some(err => errorString.includes(err)) && !this.isOnline;
}
isNetworkError(error) {
if (!error) return false;
const networkErrorPatterns = [
/fetch/i,
/network/i,
/connection/i,
/timeout/i,
/offline/i,
/ERR_NETWORK/i,
/ERR_INTERNET_DISCONNECTED/i
];
const errorString = error.toString();
return networkErrorPatterns.some(pattern => pattern.test(errorString));
}
handleInitializationError(error) {
console.error('๐จ Offline manager initialization error:', error);
// Show fallback UI
const fallback = document.createElement('div');
fallback.className = 'fixed bottom-4 right-4 bg-red-500 text-white p-4 rounded-lg shadow-lg z-50 max-w-sm';
fallback.innerHTML = `
Offline Mode Unavailable
Some offline features may not work properly.
Please ensure you have a stable internet connection.
`;
document.body.appendChild(fallback);
setTimeout(() => fallback.remove(), 8000);
}
showOfflineHelp() {
const helpModal = document.createElement('div');
helpModal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 backdrop-blur-sm';
helpModal.innerHTML = `
What works offline:
- โข App interface and navigation
- โข Previously cached resources
- โข Session data and keys (preserved in memory)
- โข Message queuing for later delivery
- โข Basic cryptographic operations
What requires internet:
- โข P2P connections (WebRTC)
- โข Real-time messaging
- โข Session verification
- โข Key exchange with new peers
Automatic sync:
When you're back online, all queued messages and actions
will be automatically synchronized. No data is lost.
Security Notice
Your encryption keys and session data remain secure even offline.
SecureBit.chat never stores sensitive information on servers.
`;
document.body.appendChild(helpModal);
}
promisifyRequest(request) {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// Public API methods
async addToQueue(type, data, priority = 1) {
return this.queueOfflineAction({ type, data, priority });
}
async forceSync() {
if (this.isOnline) {
return this.processOfflineQueue();
} else {
console.warn('โ ๏ธ Cannot sync while offline');
return Promise.resolve();
}
}
getStatus() {
return {
isOnline: this.isOnline,
queueLength: this.offlineQueue.length,
syncInProgress: this.syncInProgress,
hasOfflineDB: !!this.offlineDB,
lastSyncTime: this.lastSyncTime,
reconnectAttempts: this.reconnectAttempts
};
}
clearOfflineData() {
return Promise.all([
this.clearStoredData('offlineQueue'),
this.clearStoredData('messageQueue'),
this.clearStoredData('sessionData'),
this.clearStoredData('appState')
]).then(() => {
this.offlineQueue = [];
console.log('๐๏ธ All offline data cleared');
});
}
// Cleanup method
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) {
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');
}
}
// Singleton pattern
let instance = null;
const PWAOfflineManagerAPI = {
getInstance() {
if (!instance) {
instance = new PWAOfflineManager();
}
return instance;
},
init() {
return this.getInstance();
}
};
// Export for module use
if (typeof module !== 'undefined' && module.exports) {
module.exports = PWAOfflineManagerAPI;
} else if (typeof window !== 'undefined' && !window.PWAOfflineManager) {
window.PWAOfflineManager = PWAOfflineManagerAPI;
}
// Auto-initialize when DOM is ready
if (typeof window !== 'undefined' && !window.pwaOfflineManager) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
if (!window.pwaOfflineManager) {
window.pwaOfflineManager = PWAOfflineManagerAPI.init();
}
});
} else {
if (!window.pwaOfflineManager) {
window.pwaOfflineManager = PWAOfflineManagerAPI.init();
}
}
}