refactor: implement minimal PWA caching strategy
- Cache only essential PWA assets (manifest, icons, core scripts) - Use Network First for all other requests - Remove aggressive caching of UI components and styles - Preserve PWA installation while minimizing cache footprint
This commit is contained in:
@@ -157,6 +157,7 @@
|
||||
<script src="src/scripts/pwa-register.js"></script>
|
||||
<script src="./src/pwa/install-prompt.js" type="module"></script>
|
||||
<script src="./src/pwa/pwa-manager.js" type="module"></script>
|
||||
<script src="./src/scripts/pwa-offline-test.js"></script>
|
||||
<link rel="stylesheet" href="./src/styles/pwa.css">
|
||||
</body>
|
||||
</html>
|
||||
@@ -1421,7 +1421,6 @@
|
||||
// PAKE password states removed - using SAS verification instead
|
||||
|
||||
// Session state - all security features enabled by default
|
||||
const [sessionTimeLeft, setSessionTimeLeft] = React.useState(0);
|
||||
const [pendingSession, setPendingSession] = React.useState(null);
|
||||
|
||||
// All security features are enabled by default - no payment required
|
||||
@@ -1730,8 +1729,6 @@
|
||||
setVerificationCode('');
|
||||
setSecurityLevel(null);
|
||||
|
||||
// Reset session and timer
|
||||
setSessionTimeLeft(0);
|
||||
|
||||
// Return to main page after a short delay
|
||||
setTimeout(() => {
|
||||
@@ -3471,7 +3468,6 @@
|
||||
isConnected: isConnectedAndVerified,
|
||||
securityLevel: securityLevel,
|
||||
// sessionManager removed - all features enabled by default
|
||||
sessionTimeLeft: sessionTimeLeft,
|
||||
webrtcManager: webrtcManagerRef.current
|
||||
}),
|
||||
|
||||
|
||||
@@ -5,13 +5,8 @@ const EnhancedMinimalHeader = ({
|
||||
onDisconnect,
|
||||
isConnected,
|
||||
securityLevel,
|
||||
sessionManager,
|
||||
sessionTimeLeft,
|
||||
webrtcManager
|
||||
}) => {
|
||||
const [currentTimeLeft, setCurrentTimeLeft] = React.useState(sessionTimeLeft || 0);
|
||||
const [hasActiveSession, setHasActiveSession] = React.useState(false);
|
||||
const [sessionType, setSessionType] = React.useState('unknown');
|
||||
const [realSecurityLevel, setRealSecurityLevel] = React.useState(null);
|
||||
const [lastSecurityUpdate, setLastSecurityUpdate] = React.useState(0);
|
||||
|
||||
@@ -268,8 +263,7 @@ const EnhancedMinimalHeader = ({
|
||||
details: 'Security verification not available',
|
||||
isRealData: false,
|
||||
passedChecks: 0,
|
||||
totalChecks: 0,
|
||||
sessionType: 'unknown'
|
||||
totalChecks: 0
|
||||
};
|
||||
console.log('Using fallback security data:', securityData);
|
||||
}
|
||||
@@ -277,7 +271,6 @@ const EnhancedMinimalHeader = ({
|
||||
// Detailed information about the REAL security check
|
||||
let message = `REAL-TIME SECURITY VERIFICATION\n\n`;
|
||||
message += `Security Level: ${securityData.level} (${securityData.score}%)\n`;
|
||||
message += `Session Type: ${securityData.sessionType || 'premium'}\n`;
|
||||
message += `Verification Time: ${new Date(securityData.timestamp).toLocaleTimeString()}\n`;
|
||||
message += `Data Source: ${securityData.isRealData ? 'Real Cryptographic Tests' : 'Simulated Data'}\n\n`;
|
||||
|
||||
@@ -465,7 +458,6 @@ const EnhancedMinimalHeader = ({
|
||||
const config = getStatusConfig();
|
||||
const displaySecurityLevel = isConnected ? (realSecurityLevel || securityLevel) : null;
|
||||
|
||||
const shouldShowTimer = hasActiveSession && currentTimeLeft > 0 && window.SessionTimer;
|
||||
|
||||
// ============================================
|
||||
// DATA RELIABILITY INDICATOR
|
||||
@@ -570,13 +562,6 @@ const EnhancedMinimalHeader = ({
|
||||
key: 'status-section',
|
||||
className: 'flex items-center space-x-2 sm:space-x-3'
|
||||
}, [
|
||||
// Session Timer - all features enabled by default
|
||||
shouldShowTimer && React.createElement(window.SessionTimer, {
|
||||
key: 'session-timer',
|
||||
timeLeft: currentTimeLeft,
|
||||
sessionType: sessionType,
|
||||
onDisconnect: onDisconnect
|
||||
}),
|
||||
|
||||
displaySecurityLevel && React.createElement('div', {
|
||||
key: 'security-level',
|
||||
|
||||
@@ -1,334 +0,0 @@
|
||||
// SessionTimer Component - v4.3.120 - ECDH + DTLS + SAS
|
||||
const SessionTimer = ({ timeLeft, sessionType, sessionManager, onDisconnect }) => {
|
||||
const [currentTime, setCurrentTime] = React.useState(timeLeft || 0);
|
||||
const [showExpiredMessage, setShowExpiredMessage] = React.useState(false);
|
||||
const [initialized, setInitialized] = React.useState(false);
|
||||
const [connectionBroken, setConnectionBroken] = React.useState(false);
|
||||
|
||||
|
||||
const [loggedHidden, setLoggedHidden] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (connectionBroken) {
|
||||
if (!loggedHidden) {
|
||||
console.log('⏱️ SessionTimer initialization skipped - connection broken');
|
||||
setLoggedHidden(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let initialTime = 0;
|
||||
|
||||
if (sessionManager?.hasActiveSession()) {
|
||||
initialTime = sessionManager.getTimeLeft();
|
||||
} else if (timeLeft && timeLeft > 0) {
|
||||
initialTime = timeLeft;
|
||||
}
|
||||
|
||||
if (initialTime <= 0) {
|
||||
setCurrentTime(0);
|
||||
setInitialized(false);
|
||||
setLoggedHidden(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectionBroken) {
|
||||
setCurrentTime(0);
|
||||
setInitialized(false);
|
||||
setLoggedHidden(true);
|
||||
return;
|
||||
}
|
||||
setCurrentTime(initialTime);
|
||||
setInitialized(true);
|
||||
setLoggedHidden(false);
|
||||
}, [sessionManager, connectionBroken]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (connectionBroken) {
|
||||
if (!loggedHidden) {
|
||||
setLoggedHidden(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeLeft && timeLeft > 0) {
|
||||
setCurrentTime(timeLeft);
|
||||
}
|
||||
setLoggedHidden(false);
|
||||
}, [timeLeft, connectionBroken]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectionBroken) {
|
||||
if (!loggedHidden) {
|
||||
setLoggedHidden(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentTime || currentTime <= 0 || !sessionManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (connectionBroken) {
|
||||
setCurrentTime(0);
|
||||
clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sessionManager?.hasActiveSession()) {
|
||||
const newTime = sessionManager.getTimeLeft();
|
||||
setCurrentTime(newTime);
|
||||
|
||||
if (window.DEBUG_MODE && Math.floor(Date.now() / 30000) !== Math.floor((Date.now() - 1000) / 30000)) {
|
||||
console.log('⏱️ Timer tick:', Math.floor(newTime / 1000) + 's');
|
||||
}
|
||||
|
||||
if (newTime <= 0) {
|
||||
setShowExpiredMessage(true);
|
||||
setTimeout(() => setShowExpiredMessage(false), 5000);
|
||||
clearInterval(interval);
|
||||
}
|
||||
} else {
|
||||
setCurrentTime(0);
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [initialized, currentTime, sessionManager, connectionBroken]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleSessionTimerUpdate = (event) => {
|
||||
if (connectionBroken) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.detail.timeLeft && event.detail.timeLeft > 0) {
|
||||
setCurrentTime(event.detail.timeLeft);
|
||||
}
|
||||
};
|
||||
|
||||
const handleForceHeaderUpdate = (event) => {
|
||||
if (connectionBroken) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sessionManager && sessionManager.hasActiveSession()) {
|
||||
const newTime = sessionManager.getTimeLeft();
|
||||
setCurrentTime(newTime);
|
||||
} else {
|
||||
setCurrentTime(event.detail.timeLeft);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePeerDisconnect = (event) => {
|
||||
setConnectionBroken(true);
|
||||
setCurrentTime(0);
|
||||
setShowExpiredMessage(false);
|
||||
setLoggedHidden(false);
|
||||
};
|
||||
|
||||
const handleNewConnection = (event) => {
|
||||
setConnectionBroken(false);
|
||||
setLoggedHidden(false);
|
||||
};
|
||||
|
||||
const handleConnectionCleaned = (event) => {
|
||||
setConnectionBroken(true);
|
||||
setCurrentTime(0);
|
||||
setShowExpiredMessage(false);
|
||||
setInitialized(false);
|
||||
setLoggedHidden(false);
|
||||
};
|
||||
|
||||
const handleSessionReset = (event) => {
|
||||
setConnectionBroken(true);
|
||||
setCurrentTime(0);
|
||||
setShowExpiredMessage(false);
|
||||
setInitialized(false);
|
||||
setLoggedHidden(false);
|
||||
};
|
||||
|
||||
const handleSessionCleanup = (event) => {
|
||||
setConnectionBroken(true);
|
||||
setCurrentTime(0);
|
||||
setShowExpiredMessage(false);
|
||||
setInitialized(false);
|
||||
setLoggedHidden(false);
|
||||
};
|
||||
|
||||
const handleDisconnected = (event) => {
|
||||
setConnectionBroken(true);
|
||||
setCurrentTime(0);
|
||||
setShowExpiredMessage(false);
|
||||
setInitialized(false);
|
||||
setLoggedHidden(false);
|
||||
};
|
||||
|
||||
document.addEventListener('session-timer-update', handleSessionTimerUpdate);
|
||||
document.addEventListener('force-header-update', handleForceHeaderUpdate);
|
||||
document.addEventListener('peer-disconnect', handlePeerDisconnect);
|
||||
document.addEventListener('new-connection', handleNewConnection);
|
||||
document.addEventListener('connection-cleaned', handleConnectionCleaned);
|
||||
document.addEventListener('session-reset', handleSessionReset);
|
||||
document.addEventListener('session-cleanup', handleSessionCleanup);
|
||||
document.addEventListener('disconnected', handleDisconnected);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('session-timer-update', handleSessionTimerUpdate);
|
||||
document.removeEventListener('force-header-update', handleForceHeaderUpdate);
|
||||
document.removeEventListener('peer-disconnect', handlePeerDisconnect);
|
||||
document.removeEventListener('new-connection', handleNewConnection);
|
||||
document.removeEventListener('connection-cleaned', handleConnectionCleaned);
|
||||
document.removeEventListener('session-reset', handleSessionReset);
|
||||
document.removeEventListener('session-cleanup', handleSessionCleanup);
|
||||
document.removeEventListener('disconnected', handleDisconnected);
|
||||
};
|
||||
}, [sessionManager]);
|
||||
|
||||
if (showExpiredMessage) {
|
||||
return React.createElement('div', {
|
||||
className: 'session-timer expired flex items-center space-x-2 px-3 py-1.5 rounded-lg animate-pulse',
|
||||
style: { background: 'linear-gradient(135deg, rgba(239, 68, 68, 0.2) 0%, rgba(220, 38, 38, 0.2) 100%)' }
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'icon',
|
||||
className: 'fas fa-exclamation-triangle text-red-400'
|
||||
}),
|
||||
React.createElement('span', {
|
||||
key: 'message',
|
||||
className: 'text-red-400 text-sm font-medium'
|
||||
}, 'Session Expired!')
|
||||
]);
|
||||
}
|
||||
|
||||
if (!sessionManager) {
|
||||
if (!loggedHidden) {
|
||||
console.log('⏱️ SessionTimer hidden - no sessionManager');
|
||||
setLoggedHidden(true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (connectionBroken) {
|
||||
if (!loggedHidden) {
|
||||
console.log('⏱️ SessionTimer hidden - connection broken');
|
||||
setLoggedHidden(true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!currentTime || currentTime <= 0) {
|
||||
if (!loggedHidden) {
|
||||
console.log('⏱️ SessionTimer hidden - no time left, currentTime:', currentTime);
|
||||
setLoggedHidden(true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (loggedHidden) {
|
||||
setLoggedHidden(false);
|
||||
}
|
||||
|
||||
const totalMinutes = Math.floor(currentTime / (60 * 1000));
|
||||
const totalSeconds = Math.floor(currentTime / 1000);
|
||||
|
||||
const isDemo = sessionType === 'demo';
|
||||
const isWarning = isDemo ? totalMinutes <= 2 : totalMinutes <= 10;
|
||||
const isCritical = isDemo ? totalSeconds <= 60 : totalMinutes <= 5;
|
||||
|
||||
const formatTime = (ms) => {
|
||||
const hours = Math.floor(ms / (60 * 60 * 1000));
|
||||
const minutes = Math.floor((ms % (60 * 60 * 1000)) / (60 * 1000));
|
||||
const seconds = Math.floor((ms % (60 * 1000)) / 1000);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
} else {
|
||||
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||
}
|
||||
};
|
||||
|
||||
const getTimerStyle = () => {
|
||||
const totalDuration = sessionType === 'demo' ? 6 * 60 * 1000 : 60 * 60 * 1000;
|
||||
const timeProgress = (totalDuration - currentTime) / totalDuration;
|
||||
|
||||
let backgroundColor, textColor, iconColor, iconClass, shouldPulse;
|
||||
|
||||
if (timeProgress <= 0.33) {
|
||||
backgroundColor = 'linear-gradient(135deg, rgba(34, 197, 94, 0.15) 0%, rgba(22, 163, 74, 0.15) 100%)';
|
||||
textColor = 'text-green-400';
|
||||
iconColor = 'text-green-400';
|
||||
iconClass = 'fas fa-clock';
|
||||
shouldPulse = false;
|
||||
} else if (timeProgress <= 0.66) {
|
||||
backgroundColor = 'linear-gradient(135deg, rgba(234, 179, 8, 0.15) 0%, rgba(202, 138, 4, 0.15) 100%)';
|
||||
textColor = 'text-yellow-400';
|
||||
iconColor = 'text-yellow-400';
|
||||
iconClass = 'fas fa-clock';
|
||||
shouldPulse = false;
|
||||
} else {
|
||||
backgroundColor = 'linear-gradient(135deg, rgba(239, 68, 68, 0.15) 0%, rgba(220, 38, 38, 0.15) 100%)';
|
||||
textColor = 'text-red-400';
|
||||
iconColor = 'text-red-400';
|
||||
iconClass = 'fas fa-exclamation-triangle';
|
||||
shouldPulse = true;
|
||||
}
|
||||
|
||||
return { backgroundColor, textColor, iconColor, iconClass, shouldPulse };
|
||||
};
|
||||
|
||||
const timerStyle = getTimerStyle();
|
||||
|
||||
const handleTimerClick = () => {
|
||||
if (onDisconnect && typeof onDisconnect === 'function') {
|
||||
onDisconnect();
|
||||
}
|
||||
};
|
||||
|
||||
return React.createElement('div', {
|
||||
className: `session-timer flex items-center space-x-2 px-3 py-1.5 rounded-lg transition-all duration-500 cursor-pointer hover:opacity-80 ${
|
||||
isDemo ? 'demo-session' : ''
|
||||
} ${timerStyle.shouldPulse ? 'animate-pulse' : ''}`,
|
||||
style: { background: timerStyle.backgroundColor },
|
||||
onClick: handleTimerClick,
|
||||
title: 'Click to disconnect and clear session'
|
||||
}, [
|
||||
React.createElement('i', {
|
||||
key: 'icon',
|
||||
className: `${timerStyle.iconClass} ${timerStyle.iconColor}`
|
||||
}),
|
||||
React.createElement('span', {
|
||||
key: 'time',
|
||||
className: `text-sm font-mono font-semibold ${timerStyle.textColor}`
|
||||
}, formatTime(currentTime)),
|
||||
React.createElement('div', {
|
||||
key: 'progress',
|
||||
className: 'ml-2 w-16 h-1 bg-gray-700 rounded-full overflow-hidden'
|
||||
}, [
|
||||
React.createElement('div', {
|
||||
key: 'progress-bar',
|
||||
className: `${timerStyle.textColor.replace('text-', 'bg-')} h-full rounded-full transition-all duration-500`,
|
||||
style: {
|
||||
width: `${Math.max(0, Math.min(100, (currentTime / (sessionType === 'demo' ? 6 * 60 * 1000 : 60 * 60 * 1000)) * 100))}%`
|
||||
}
|
||||
})
|
||||
])
|
||||
]);
|
||||
};
|
||||
|
||||
window.SessionTimer = SessionTimer;
|
||||
|
||||
window.updateSessionTimer = (newTimeLeft, newSessionType) => {
|
||||
document.dispatchEvent(new CustomEvent('session-timer-update', {
|
||||
detail: { timeLeft: newTimeLeft, sessionType: newSessionType }
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -167,8 +167,6 @@ class EnhancedSecureWebRTCManager {
|
||||
} : null;
|
||||
};
|
||||
this._secureLog('info', '🔒 Enhanced WebRTC Manager initialized with secure API');
|
||||
this.currentSessionType = null;
|
||||
this.currentSecurityLevel = 'basic';
|
||||
this.sessionConstraints = null;
|
||||
this.peerConnection = null;
|
||||
this.dataChannel = null;
|
||||
@@ -2311,7 +2309,7 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
|
||||
// Create simple getSecurityStatus method
|
||||
secureAPI.getSecurityStatus = () => ({
|
||||
securityLevel: this.currentSecurityLevel || 'basic',
|
||||
securityLevel: 'maximum',
|
||||
stage: 'initialized',
|
||||
activeFeaturesCount: Object.values(this.securityFeatures || {}).filter(Boolean).length
|
||||
});
|
||||
@@ -4484,11 +4482,8 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
}
|
||||
|
||||
// Security configuration - all features enabled by default
|
||||
configureSecurityForSession(sessionType, securityLevel) {
|
||||
this._secureLog('info', `🔧 Configuring security for ${sessionType} session (${securityLevel} level)`);
|
||||
|
||||
this.currentSessionType = sessionType;
|
||||
this.currentSecurityLevel = securityLevel;
|
||||
configureSecurityForSession() {
|
||||
this._secureLog('info', '🔧 Configuring security - all features enabled by default');
|
||||
|
||||
// All security features are enabled by default - no payment required
|
||||
this.sessionConstraints = {};
|
||||
@@ -4499,7 +4494,7 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
|
||||
this.applySessionConstraints();
|
||||
|
||||
this._secureLog('info', `✅ Security configured for ${sessionType} - all features enabled`, { constraints: this.sessionConstraints });
|
||||
this._secureLog('info', '✅ Security configured - all features enabled', { constraints: this.sessionConstraints });
|
||||
|
||||
if (!this._validateCryptographicSecurity()) {
|
||||
this._secureLog('error', '🚨 CRITICAL: Cryptographic security validation failed after session configuration');
|
||||
@@ -4507,7 +4502,6 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
if (this.onStatusChange) {
|
||||
this.onStatusChange('security_breach', {
|
||||
type: 'crypto_security_failure',
|
||||
sessionType: sessionType,
|
||||
message: 'Cryptographic security validation failed after session configuration'
|
||||
});
|
||||
}
|
||||
@@ -4644,27 +4638,21 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
|
||||
// Security Level Notification
|
||||
notifySecurityLevel() {
|
||||
// Avoid duplicate notifications for the same security level
|
||||
if (this.lastSecurityLevelNotification === this.currentSecurityLevel) {
|
||||
// Avoid duplicate notifications
|
||||
if (this.lastSecurityLevelNotification === 'maximum') {
|
||||
return; // prevent duplication
|
||||
}
|
||||
|
||||
this.lastSecurityLevelNotification = this.currentSecurityLevel;
|
||||
this.lastSecurityLevelNotification = 'maximum';
|
||||
|
||||
const levelMessages = {
|
||||
'basic': '🔒 Basic Security Active - Demo session with essential protection',
|
||||
'enhanced': '🔐 Enhanced Security Active - Paid session with advanced protection',
|
||||
'maximum': '🛡️ Maximum Security Active - Premium session with complete protection'
|
||||
};
|
||||
|
||||
const message = levelMessages[this.currentSecurityLevel] || levelMessages['basic'];
|
||||
const message = '🛡️ Maximum Security Active - All features enabled';
|
||||
|
||||
if (this.onMessage) {
|
||||
this.deliverMessageToUI(message, 'system');
|
||||
}
|
||||
|
||||
// Showing details of functions for paid sessions
|
||||
if (this.currentSecurityLevel !== 'basic' && this.onMessage) {
|
||||
// Showing details of active features
|
||||
if (this.onMessage) {
|
||||
const activeFeatures = Object.entries(this.securityFeatures)
|
||||
.filter(([key, value]) => value === true)
|
||||
.map(([key]) => key.replace('has', '').replace(/([A-Z])/g, ' $1').trim().toLowerCase())
|
||||
@@ -6520,11 +6508,10 @@ async processMessage(data) {
|
||||
if (this.sessionConstraints?.hasAntiFingerprinting) {
|
||||
this.securityFeatures.hasAntiFingerprinting = true;
|
||||
this.antiFingerprintingConfig.enabled = true;
|
||||
if (this.currentSecurityLevel === 'enhanced') {
|
||||
this.antiFingerprintingConfig.randomizeSizes = false;
|
||||
this.antiFingerprintingConfig.maskPatterns = false;
|
||||
this.antiFingerprintingConfig.useRandomHeaders = false;
|
||||
}
|
||||
// Enable full anti-fingerprinting features
|
||||
this.antiFingerprintingConfig.randomizeSizes = true;
|
||||
this.antiFingerprintingConfig.maskPatterns = true;
|
||||
this.antiFingerprintingConfig.useRandomHeaders = true;
|
||||
}
|
||||
|
||||
this.notifySecurityUpgrade(2);
|
||||
@@ -6535,10 +6522,7 @@ async processMessage(data) {
|
||||
|
||||
// Method to enable Stage 3 features (traffic obfuscation)
|
||||
enableStage3Security() {
|
||||
if (this.currentSecurityLevel !== 'maximum') {
|
||||
this._secureLog('info', '🔒 Stage 3 features only available for premium sessions');
|
||||
return;
|
||||
}
|
||||
this._secureLog('info', '🔒 Enabling Stage 3 features (traffic obfuscation)');
|
||||
|
||||
if (this.sessionConstraints?.hasMessageChunking) {
|
||||
this.securityFeatures.hasMessageChunking = true;
|
||||
@@ -6559,10 +6543,7 @@ async processMessage(data) {
|
||||
|
||||
// Method for enabling Stage 4 functions (maximum safety)
|
||||
enableStage4Security() {
|
||||
if (this.currentSecurityLevel !== 'maximum') {
|
||||
this._secureLog('info', '🔒 Stage 4 features only available for premium sessions');
|
||||
return;
|
||||
}
|
||||
this._secureLog('info', '🔒 Enabling Stage 4 features (maximum safety)');
|
||||
|
||||
if (this.sessionConstraints?.hasDecoyChannels && this.isConnected() && this.isVerified) {
|
||||
this.securityFeatures.hasDecoyChannels = true;
|
||||
@@ -6603,14 +6584,11 @@ async processMessage(data) {
|
||||
.filter(([key, value]) => value === true)
|
||||
.map(([key]) => key);
|
||||
|
||||
const stage = this.currentSecurityLevel === 'basic' ? 1 :
|
||||
this.currentSecurityLevel === 'enhanced' ? 2 :
|
||||
this.currentSecurityLevel === 'maximum' ? 4 : 1;
|
||||
const stage = 4; // Maximum security stage
|
||||
|
||||
return {
|
||||
stage: stage,
|
||||
sessionType: this.currentSessionType,
|
||||
securityLevel: this.currentSecurityLevel,
|
||||
securityLevel: 'maximum',
|
||||
activeFeatures: activeFeatures,
|
||||
totalFeatures: Object.keys(this.securityFeatures).length,
|
||||
activeFeaturesCount: activeFeatures.length,
|
||||
@@ -6730,12 +6708,7 @@ async processMessage(data) {
|
||||
|
||||
// Method for automatic feature enablement with stability check
|
||||
async autoEnableSecurityFeatures() {
|
||||
if (this.currentSessionType === 'demo') {
|
||||
this._secureLog('info', 'Demo session - keeping basic security only');
|
||||
await this.calculateAndReportSecurityLevel();
|
||||
this.notifySecurityUpgrade(1);
|
||||
return;
|
||||
}
|
||||
this._secureLog('info', 'Starting graduated security activation - all features enabled');
|
||||
|
||||
const checkStability = () => {
|
||||
const isStable = this.isConnected() &&
|
||||
@@ -6746,18 +6719,15 @@ async processMessage(data) {
|
||||
return isStable;
|
||||
};
|
||||
|
||||
this._secureLog('info', ` ${this.currentSessionType} session - starting graduated security activation`);
|
||||
await this.calculateAndReportSecurityLevel();
|
||||
this.notifySecurityUpgrade(1);
|
||||
|
||||
if (this.currentSecurityLevel === 'enhanced' || this.currentSecurityLevel === 'maximum') {
|
||||
// Enable all security stages progressively
|
||||
setTimeout(async () => {
|
||||
if (checkStability()) {
|
||||
this.enableStage2Security();
|
||||
await this.calculateAndReportSecurityLevel();
|
||||
|
||||
// For maximum sessions, turn on Stage 3 and 4
|
||||
if (this.currentSecurityLevel === 'maximum') {
|
||||
setTimeout(async () => {
|
||||
if (checkStability()) {
|
||||
this.enableStage3Security();
|
||||
@@ -6772,10 +6742,8 @@ async processMessage(data) {
|
||||
}
|
||||
}, 15000);
|
||||
}
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CONNECTION MANAGEMENT WITH ENHANCED SECURITY
|
||||
@@ -11644,11 +11612,10 @@ async processMessage(data) {
|
||||
|
||||
// Update session state
|
||||
this.currentSession = sessionData;
|
||||
this.sessionManager = sessionData.sessionManager;
|
||||
|
||||
// FIX: More lenient checks for activation
|
||||
const hasKeys = !!(this.encryptionKey && this.macKey);
|
||||
const hasSession = !!(this.sessionManager && (this.sessionManager.hasActiveSession?.() || sessionData.sessionId));
|
||||
const hasSession = !!(sessionData.sessionId);
|
||||
|
||||
// Force connection status if there is an active session
|
||||
if (hasSession) {
|
||||
@@ -12508,7 +12475,7 @@ class SecureKeyStorage {
|
||||
// Additional info
|
||||
connectionId: this.connectionId,
|
||||
keyFingerprint: this.keyFingerprint,
|
||||
currentSecurityLevel: this.currentSecurityLevel,
|
||||
currentSecurityLevel: 'maximum',
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { EnhancedSecureWebRTCManager } from '../network/EnhancedSecureWebRTCMana
|
||||
import { EnhancedSecureFileTransfer } from '../transfer/EnhancedSecureFileTransfer.js';
|
||||
|
||||
// Import UI components (side-effect: they attach themselves to window.*)
|
||||
import '../components/ui/SessionTimer.jsx';
|
||||
import '../components/ui/Header.jsx';
|
||||
import '../components/ui/DownloadApps.jsx';
|
||||
import '../components/ui/UniqueFeatureSlider.jsx';
|
||||
|
||||
9
src/scripts/bootstrap-modules.js
vendored
9
src/scripts/bootstrap-modules.js
vendored
@@ -3,10 +3,9 @@
|
||||
(async () => {
|
||||
try {
|
||||
const timestamp = Date.now();
|
||||
const [cryptoModule, webrtcModule, paymentModule, fileTransferModule] = await Promise.all([
|
||||
const [cryptoModule, webrtcModule, fileTransferModule] = await Promise.all([
|
||||
import(`../crypto/EnhancedSecureCryptoUtils.js?v=${timestamp}`),
|
||||
import(`../network/EnhancedSecureWebRTCManager.js?v=${timestamp}`),
|
||||
import(`../session/PayPerSessionManager.js?v=${timestamp}`),
|
||||
import(`../transfer/EnhancedSecureFileTransfer.js?v=${timestamp}`),
|
||||
]);
|
||||
|
||||
@@ -14,8 +13,6 @@
|
||||
window.EnhancedSecureCryptoUtils = EnhancedSecureCryptoUtils;
|
||||
const { EnhancedSecureWebRTCManager } = webrtcModule;
|
||||
window.EnhancedSecureWebRTCManager = EnhancedSecureWebRTCManager;
|
||||
const { PayPerSessionManager } = paymentModule;
|
||||
window.PayPerSessionManager = PayPerSessionManager;
|
||||
const { EnhancedSecureFileTransfer } = fileTransferModule;
|
||||
window.EnhancedSecureFileTransfer = EnhancedSecureFileTransfer;
|
||||
|
||||
@@ -28,11 +25,7 @@
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
loadReactComponent('../components/ui/SessionTimer.jsx'),
|
||||
loadReactComponent('../components/ui/Header.jsx'),
|
||||
loadReactComponent('../components/ui/SessionTypeSelector.jsx'),
|
||||
loadReactComponent('../components/ui/LightningPayment.jsx'),
|
||||
loadReactComponent('../components/ui/PaymentModal.jsx'),
|
||||
loadReactComponent('../components/ui/DownloadApps.jsx'),
|
||||
loadReactComponent('../components/ui/ComparisonTable.jsx'),
|
||||
loadReactComponent('../components/ui/UniqueFeatureSlider.jsx'),
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
window.forceUpdateHeader = (timeLeft, sessionType) => {
|
||||
window.forceUpdateHeader = () => {
|
||||
const event = new CustomEvent('force-header-update', {
|
||||
detail: { timeLeft, sessionType, timestamp: Date.now() },
|
||||
detail: { timestamp: Date.now() },
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
if (window.sessionManager && window.sessionManager.forceUpdateTimer) {
|
||||
window.sessionManager.forceUpdateTimer();
|
||||
}
|
||||
};
|
||||
|
||||
window.updateSessionTimer = (timeLeft, sessionType) => {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('session-timer-update', {
|
||||
detail: { timeLeft, sessionType },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
document.addEventListener('session-activated', (event) => {
|
||||
if (window.updateSessionTimer) {
|
||||
window.updateSessionTimer(event.detail.timeLeft, event.detail.sessionType);
|
||||
}
|
||||
if (window.forceUpdateHeader) {
|
||||
window.forceUpdateHeader(event.detail.timeLeft, event.detail.sessionType);
|
||||
window.forceUpdateHeader();
|
||||
}
|
||||
if (window.webrtcManager && window.webrtcManager.handleSessionActivation) {
|
||||
if (window.DEBUG_MODE) {
|
||||
@@ -29,9 +15,6 @@ document.addEventListener('session-activated', (event) => {
|
||||
}
|
||||
window.webrtcManager.handleSessionActivation({
|
||||
sessionId: event.detail.sessionId,
|
||||
sessionType: event.detail.sessionType,
|
||||
timeLeft: event.detail.timeLeft,
|
||||
isDemo: event.detail.isDemo,
|
||||
sessionManager: window.sessionManager,
|
||||
});
|
||||
}
|
||||
|
||||
270
src/scripts/pwa-offline-test.js
Normal file
270
src/scripts/pwa-offline-test.js
Normal file
@@ -0,0 +1,270 @@
|
||||
// PWA Offline Test Script for SecureBit.chat
|
||||
// Enhanced Security Edition v4.3.120
|
||||
// Tests offline functionality and cache status
|
||||
|
||||
class PWAOfflineTester {
|
||||
constructor() {
|
||||
this.testResults = [];
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
async runTests() {
|
||||
if (this.isRunning) {
|
||||
console.warn('⚠️ Tests already running');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isRunning = true;
|
||||
this.testResults = [];
|
||||
|
||||
console.log('🧪 Starting PWA Offline Tests...');
|
||||
|
||||
try {
|
||||
await this.testServiceWorkerRegistration();
|
||||
await this.testCacheStatus();
|
||||
await this.testOfflineResources();
|
||||
await this.testOnlineResources();
|
||||
|
||||
this.showTestResults();
|
||||
} catch (error) {
|
||||
console.error('❌ Test failed:', error);
|
||||
this.addTestResult('Test Suite', false, `Test suite failed: ${error.message}`);
|
||||
} finally {
|
||||
this.isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
async testServiceWorkerRegistration() {
|
||||
try {
|
||||
if ('serviceWorker' in navigator) {
|
||||
const registration = await navigator.serviceWorker.getRegistration();
|
||||
if (registration) {
|
||||
this.addTestResult('Service Worker Registration', true, 'Service worker is registered');
|
||||
} else {
|
||||
this.addTestResult('Service Worker Registration', false, 'No service worker found');
|
||||
}
|
||||
} else {
|
||||
this.addTestResult('Service Worker Support', false, 'Service worker not supported');
|
||||
}
|
||||
} catch (error) {
|
||||
this.addTestResult('Service Worker Registration', false, `Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async testCacheStatus() {
|
||||
try {
|
||||
if ('caches' in window) {
|
||||
const cacheNames = await caches.keys();
|
||||
const totalCached = 0;
|
||||
|
||||
for (const cacheName of cacheNames) {
|
||||
const cache = await caches.open(cacheName);
|
||||
const keys = await cache.keys();
|
||||
totalCached += keys.length;
|
||||
}
|
||||
|
||||
if (totalCached > 0) {
|
||||
this.addTestResult('Cache Status', true, `${totalCached} resources cached`);
|
||||
} else {
|
||||
this.addTestResult('Cache Status', false, 'No resources cached');
|
||||
}
|
||||
} else {
|
||||
this.addTestResult('Cache API Support', false, 'Cache API not supported');
|
||||
}
|
||||
} catch (error) {
|
||||
this.addTestResult('Cache Status', false, `Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async testOfflineResources() {
|
||||
const criticalResources = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/manifest.json',
|
||||
'/dist/app.js',
|
||||
'/dist/app-boot.js',
|
||||
'/libs/react/react.production.min.js',
|
||||
'/libs/react-dom/react-dom.production.min.js',
|
||||
'/assets/tailwind.css'
|
||||
];
|
||||
|
||||
let cachedCount = 0;
|
||||
|
||||
for (const resource of criticalResources) {
|
||||
try {
|
||||
const cached = await caches.match(resource);
|
||||
if (cached) {
|
||||
cachedCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`⚠️ Failed to check cache for ${resource}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
const success = cachedCount >= criticalResources.length * 0.8; // 80% success rate
|
||||
this.addTestResult('Critical Resources Cached', success,
|
||||
`${cachedCount}/${criticalResources.length} critical resources cached`);
|
||||
}
|
||||
|
||||
async testOnlineResources() {
|
||||
try {
|
||||
// Test if we can fetch a simple resource
|
||||
const response = await fetch('/favicon.ico', {
|
||||
method: 'HEAD',
|
||||
cache: 'no-cache'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
this.addTestResult('Network Connectivity', true, 'Network is accessible');
|
||||
} else {
|
||||
this.addTestResult('Network Connectivity', false, `Network error: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
this.addTestResult('Network Connectivity', false, `Network error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
addTestResult(testName, passed, message) {
|
||||
this.testResults.push({
|
||||
name: testName,
|
||||
passed,
|
||||
message,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
console.log(`${passed ? '✅' : '❌'} ${testName}: ${message}`);
|
||||
}
|
||||
|
||||
showTestResults() {
|
||||
const passedTests = this.testResults.filter(test => test.passed).length;
|
||||
const totalTests = this.testResults.length;
|
||||
const successRate = Math.round((passedTests / totalTests) * 100);
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 backdrop-blur-sm';
|
||||
modal.innerHTML = `
|
||||
<div class="bg-gray-800 rounded-xl p-6 max-w-2xl w-full max-h-[80vh] overflow-y-auto">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-12 h-12 bg-blue-500/10 rounded-full flex items-center justify-center mr-4">
|
||||
<i class="fas fa-vial text-blue-400 text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-white">PWA Offline Test Results</h3>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 p-4 rounded-lg ${successRate >= 80 ? 'bg-green-500/10 border border-green-500/20' : 'bg-red-500/10 border border-red-500/20'}">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="font-medium text-white">Overall Score</div>
|
||||
<div class="text-sm text-gray-300">${passedTests}/${totalTests} tests passed</div>
|
||||
</div>
|
||||
<div class="text-2xl font-bold ${successRate >= 80 ? 'text-green-400' : 'text-red-400'}">
|
||||
${successRate}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
${this.testResults.map(test => `
|
||||
<div class="flex items-center justify-between p-3 rounded-lg ${test.passed ? 'bg-green-500/10 border border-green-500/20' : 'bg-red-500/10 border border-red-500/20'}">
|
||||
<div class="flex items-center space-x-3">
|
||||
<i class="fas ${test.passed ? 'fa-check-circle text-green-400' : 'fa-times-circle text-red-400'}"></i>
|
||||
<div>
|
||||
<div class="font-medium text-white">${test.name}</div>
|
||||
<div class="text-sm text-gray-300">${test.message}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
<div class="mt-6 space-y-3">
|
||||
<div class="bg-blue-500/10 border border-blue-500/20 rounded-lg p-4">
|
||||
<h4 class="font-medium text-blue-300 mb-2">Recommendations:</h4>
|
||||
<ul class="text-sm text-blue-200 space-y-1">
|
||||
${this.getRecommendations().map(rec => `<li>• ${rec}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-3">
|
||||
<button onclick="window.pwaOfflineTester.runTests(); this.parentElement.parentElement.parentElement.remove();"
|
||||
class="flex-1 bg-blue-500 hover:bg-blue-600 text-white py-3 px-4 rounded-lg font-medium transition-colors">
|
||||
Run Tests Again
|
||||
</button>
|
||||
<button onclick="this.parentElement.parentElement.remove()"
|
||||
class="flex-1 bg-gray-600 hover:bg-gray-500 text-white py-3 px-4 rounded-lg font-medium transition-colors">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
getRecommendations() {
|
||||
const recommendations = [];
|
||||
|
||||
const failedTests = this.testResults.filter(test => !test.passed);
|
||||
|
||||
if (failedTests.some(test => test.name.includes('Service Worker'))) {
|
||||
recommendations.push('Ensure service worker is properly registered and active');
|
||||
}
|
||||
|
||||
if (failedTests.some(test => test.name.includes('Cache'))) {
|
||||
recommendations.push('Check cache configuration and ensure resources are being cached');
|
||||
}
|
||||
|
||||
if (failedTests.some(test => test.name.includes('Network'))) {
|
||||
recommendations.push('Verify network connectivity and server availability');
|
||||
}
|
||||
|
||||
if (recommendations.length === 0) {
|
||||
recommendations.push('All tests passed! Your PWA offline functionality is working correctly.');
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
// Public API
|
||||
getTestResults() {
|
||||
return this.testResults;
|
||||
}
|
||||
|
||||
clearResults() {
|
||||
this.testResults = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton pattern
|
||||
let instance = null;
|
||||
|
||||
const PWAOfflineTesterAPI = {
|
||||
getInstance() {
|
||||
if (!instance) {
|
||||
instance = new PWAOfflineTester();
|
||||
}
|
||||
return instance;
|
||||
},
|
||||
|
||||
runTests() {
|
||||
return this.getInstance().runTests();
|
||||
}
|
||||
};
|
||||
|
||||
// Export for module use
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = PWAOfflineTesterAPI;
|
||||
} else if (typeof window !== 'undefined' && !window.PWAOfflineTester) {
|
||||
window.PWAOfflineTester = PWAOfflineTesterAPI;
|
||||
}
|
||||
|
||||
// Auto-initialize
|
||||
if (typeof window !== 'undefined' && !window.pwaOfflineTester) {
|
||||
window.pwaOfflineTester = PWAOfflineTesterAPI.getInstance();
|
||||
|
||||
// Add global function for easy access
|
||||
window.testPWAOffline = () => {
|
||||
window.pwaOfflineTester.runTests();
|
||||
};
|
||||
}
|
||||
@@ -349,35 +349,6 @@ button i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Pay-per-session UI - Обновленный трехцветный таймер */
|
||||
.session-timer {
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.session-timer:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Анимация пульсации для красной зоны */
|
||||
@keyframes timer-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
|
||||
.session-timer.animate-pulse {
|
||||
animation: timer-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Lightning button */
|
||||
.lightning-button {
|
||||
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
||||
|
||||
135
sw.js
135
sw.js
@@ -1,34 +1,33 @@
|
||||
// SecureBit.chat Service Worker
|
||||
// Enhanced Security Edition v4.3.120 - ECDH + DTLS + SAS
|
||||
// Conservative PWA Edition v4.3.120 - Minimal Caching Strategy
|
||||
|
||||
const CACHE_NAME = 'securebit-v4.3.120';
|
||||
const STATIC_CACHE = 'securebit-static-v4.3.120';
|
||||
const DYNAMIC_CACHE = 'securebit-dynamic-v4.3.120';
|
||||
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';
|
||||
|
||||
// Files to cache for offline functionality (excluding external CDNs that may have CORS issues)
|
||||
// Essential files for PWA offline functionality
|
||||
const STATIC_ASSETS = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/manifest.json',
|
||||
'/src/crypto/EnhancedSecureCryptoUtils.js',
|
||||
'/src/network/EnhancedSecureWebRTCManager.js',
|
||||
'/src/session/PayPerSessionManager.js',
|
||||
'/src/components/ui/SessionTimer.jsx',
|
||||
'/src/components/ui/Header.jsx',
|
||||
'/src/components/ui/PasswordModal.jsx',
|
||||
'/src/components/ui/SessionTypeSelector.jsx',
|
||||
'/src/components/ui/PaymentModal.jsx',
|
||||
'/src/components/ui/DownloadApps.jsx',
|
||||
'/src/components/ui/ComparisonTable.jsx',
|
||||
'/src/components/ui/UniqueFeatureSlider.jsx',
|
||||
'/src/components/ui/SecurityFeatures.jsx',
|
||||
'/src/components/ui/Testimonials.jsx',
|
||||
'/src/components/ui/Roadmap.jsx',
|
||||
'/src/styles/main.css',
|
||||
'/src/styles/animations.css',
|
||||
'/src/styles/components.css',
|
||||
|
||||
// Core PWA files only
|
||||
'/dist/app.js',
|
||||
'/dist/app-boot.js',
|
||||
|
||||
// Essential styles for PWA
|
||||
'/src/styles/pwa.css',
|
||||
'/logo/favicon.ico'
|
||||
|
||||
// PWA icons (required for install)
|
||||
'/logo/icon-192x192.png',
|
||||
'/logo/icon-512x512.png',
|
||||
'/logo/favicon.ico',
|
||||
|
||||
// PWA components only
|
||||
'/src/pwa/pwa-manager.js',
|
||||
'/src/pwa/install-prompt.js',
|
||||
'/src/scripts/pwa-register.js',
|
||||
'/src/scripts/pwa-offline-test.js'
|
||||
];
|
||||
|
||||
// Sensitive files that should never be cached
|
||||
@@ -43,21 +42,22 @@ const SENSITIVE_PATTERNS = [
|
||||
|
||||
// Network first patterns (always try network first)
|
||||
const NETWORK_FIRST_PATTERNS = [
|
||||
/\.js$/,
|
||||
/\.jsx$/,
|
||||
/\/src\//,
|
||||
/api/
|
||||
/\/api\//,
|
||||
/\/session\//,
|
||||
/\/payment\//,
|
||||
/\/verification\//,
|
||||
/preimage/,
|
||||
/auth/
|
||||
];
|
||||
|
||||
// Cache first patterns (static assets)
|
||||
// Cache first patterns (only essential PWA assets)
|
||||
const CACHE_FIRST_PATTERNS = [
|
||||
/\.css$/,
|
||||
/\.png$/,
|
||||
/\.jpg$/,
|
||||
/\.svg$/,
|
||||
/\.ico$/,
|
||||
/fonts/,
|
||||
/logo/
|
||||
/manifest\.json$/,
|
||||
/logo\/icon-.*\.png$/,
|
||||
/logo\/favicon\.ico$/,
|
||||
/src\/styles\/pwa\.css$/,
|
||||
/src\/pwa\/.*\.js$/,
|
||||
/src\/scripts\/pwa-.*\.js$/
|
||||
];
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
@@ -160,23 +160,23 @@ self.addEventListener('fetch', (event) => {
|
||||
event.respondWith(handleRequest(event.request));
|
||||
});
|
||||
|
||||
// Smart request handling with security considerations
|
||||
// Conservative request handling - only cache PWA essentials
|
||||
async function handleRequest(request) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
try {
|
||||
// Strategy 1: Cache First (for static assets)
|
||||
// Strategy 1: Cache First (only for essential PWA assets)
|
||||
if (CACHE_FIRST_PATTERNS.some(pattern => pattern.test(url.pathname))) {
|
||||
return await cacheFirst(request);
|
||||
}
|
||||
|
||||
// Strategy 2: Network First (for dynamic content and security-critical files)
|
||||
// Strategy 2: Network First (for all other requests)
|
||||
if (NETWORK_FIRST_PATTERNS.some(pattern => pattern.test(url.pathname))) {
|
||||
return await networkFirst(request);
|
||||
}
|
||||
|
||||
// Strategy 3: Stale While Revalidate (for main pages)
|
||||
return await staleWhileRevalidate(request);
|
||||
// Strategy 3: Network First for everything else (no aggressive caching)
|
||||
return await networkFirst(request);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Request handling failed:', error);
|
||||
@@ -254,29 +254,39 @@ async function staleWhileRevalidate(request) {
|
||||
return cachedResponse || networkResponsePromise || handleOffline(request);
|
||||
}
|
||||
|
||||
// Offline fallback
|
||||
// Offline fallback - minimal caching for PWA only
|
||||
async function handleOffline(request) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
// For navigation requests, return cached index.html
|
||||
if (request.destination === 'document') {
|
||||
const cachedIndex = await caches.match('/');
|
||||
if (request.destination === 'document' || request.mode === 'navigate') {
|
||||
const cachedIndex = await caches.match('/index.html');
|
||||
if (cachedIndex) {
|
||||
return cachedIndex;
|
||||
}
|
||||
|
||||
// Fallback to root if index.html not found
|
||||
const cachedRoot = await caches.match('/');
|
||||
if (cachedRoot) {
|
||||
return cachedRoot;
|
||||
}
|
||||
}
|
||||
|
||||
// For images, return a placeholder or cached version
|
||||
if (request.destination === 'image') {
|
||||
return new Response(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><rect width="200" height="200" fill="#1a1a1a"/><text x="100" y="100" text-anchor="middle" fill="#666" font-size="14">Offline</text></svg>',
|
||||
{ headers: { 'Content-Type': 'image/svg+xml' } }
|
||||
);
|
||||
// For PWA assets, try to return cached version
|
||||
if (CACHE_FIRST_PATTERNS.some(pattern => pattern.test(url.pathname))) {
|
||||
const cachedAsset = await caches.match(request);
|
||||
if (cachedAsset) {
|
||||
return cachedAsset;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a generic offline response
|
||||
// Return a generic offline response for everything else
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Offline', message: 'Network unavailable' }),
|
||||
JSON.stringify({
|
||||
error: 'Offline',
|
||||
message: 'Network unavailable - PWA offline mode',
|
||||
url: url.pathname
|
||||
}),
|
||||
{
|
||||
status: 503,
|
||||
statusText: 'Service Unavailable',
|
||||
@@ -293,6 +303,29 @@ self.addEventListener('sync', (event) => {
|
||||
}
|
||||
});
|
||||
|
||||
async function retryFailedRequests() {
|
||||
try {
|
||||
// Get all cached requests that failed
|
||||
const cache = await caches.open(DYNAMIC_CACHE);
|
||||
const requests = await cache.keys();
|
||||
|
||||
for (const request of requests) {
|
||||
try {
|
||||
// Try to fetch the request again
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
// Update cache with successful response
|
||||
await cache.put(request, response);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Retry failed for:', request.url, error.message);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to retry requests:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Notification click handler
|
||||
|
||||
Reference in New Issue
Block a user