diff --git a/src/pwa/install-prompt.js b/src/pwa/install-prompt.js index 92e5212..5f0b951 100644 --- a/src/pwa/install-prompt.js +++ b/src/pwa/install-prompt.js @@ -14,7 +14,15 @@ class PWAInstallPrompt { init() { console.log('💿 PWA Install Prompt initializing...'); + // Сначала проверяем статус установки this.checkInstallationStatus(); + + // Если уже установлено, не инициализируем остальное + if (this.isInstalled) { + console.log('💿 App already installed, skipping initialization'); + return; + } + this.setupEventListeners(); this.createInstallButton(); this.loadInstallPreferences(); @@ -28,16 +36,26 @@ class PWAInstallPrompt { } checkInstallationStatus() { + console.log('🔍 Checking PWA installation status...'); + // Проверяем различные способы определения установки PWA const isStandalone = window.matchMedia('(display-mode: standalone)').matches; const isIOSStandalone = window.navigator.standalone === true; const hasInstallPreference = this.loadInstallPreferences().installed; + console.log('🔍 PWA Installation Check:', { + isStandalone, + isIOSStandalone, + hasInstallPreference, + userAgent: navigator.userAgent.slice(0, 100) + }); + // Проверяем, установлено ли приложение if (isStandalone || isIOSStandalone || hasInstallPreference) { this.isInstalled = true; console.log('📱 App is already installed as PWA'); document.body.classList.add('pwa-installed'); + document.body.classList.remove('pwa-browser'); // Скрываем все промпты установки this.hideInstallPrompts(); @@ -52,7 +70,9 @@ class PWAInstallPrompt { } // Если не установлено, добавляем соответствующие классы + this.isInstalled = false; document.body.classList.add('pwa-browser'); + document.body.classList.remove('pwa-installed'); if (this.isIOSSafari()) { document.body.classList.add('ios-safari'); @@ -74,7 +94,7 @@ class PWAInstallPrompt { this.isInstalled = true; this.hideInstallPrompts(); this.showInstallSuccess(); - document.body.classList.remove('pwa-browser'); + document.body.classList.remove('pwa-browser', 'ios-safari'); document.body.classList.add('pwa-installed', 'ios-pwa'); // Сохраняем предпочтение установки @@ -93,6 +113,11 @@ class PWAInstallPrompt { setTimeout(checkStandalone, 1000); } }); + + // Проверяем при фокусе окна + window.addEventListener('focus', () => { + setTimeout(checkStandalone, 500); + }); } setupEventListeners() { @@ -101,9 +126,14 @@ class PWAInstallPrompt { event.preventDefault(); this.deferredPrompt = event; + // Повторно проверяем статус установки + if (this.checkInstallationStatus()) { + return; // Если установлено, не показываем промпт + } + // Показываем промпт только если приложение не установлено if (!this.isInstalled && this.shouldShowPrompt()) { - this.showInstallOptions(); + setTimeout(() => this.showInstallOptions(), 1000); } }); @@ -114,40 +144,49 @@ class PWAInstallPrompt { this.showInstallSuccess(); this.saveInstallPreference('installed', true); - document.body.classList.remove('pwa-browser'); + document.body.classList.remove('pwa-browser', 'ios-safari'); document.body.classList.add('pwa-installed'); }); - // Дополнительная проверка для iOS - if (this.isIOSSafari()) { - let wasStandalone = window.navigator.standalone; + // Дополнительная проверка для всех устройств при изменении видимости + window.addEventListener('visibilitychange', () => { + if (document.hidden) return; - window.addEventListener('visibilitychange', () => { - if (document.hidden) return; + setTimeout(() => { + const wasInstalled = this.isInstalled; + this.checkInstallationStatus(); - setTimeout(() => { - const isStandalone = window.navigator.standalone; - - if (isStandalone && !wasStandalone && !this.isInstalled) { - console.log('✅ iOS PWA installation detected'); - this.isInstalled = true; - this.hideInstallPrompts(); - this.showInstallSuccess(); - document.body.classList.remove('pwa-browser'); - document.body.classList.add('pwa-installed', 'ios-pwa'); - - // Сохраняем предпочтение установки - this.saveInstallPreference('installed', true); - } - - wasStandalone = isStandalone; - }, 1000); - }); - } + // Если статус изменился с "не установлено" на "установлено" + if (!wasInstalled && this.isInstalled) { + console.log('✅ PWA installation detected on visibility change'); + this.hideInstallPrompts(); + this.showInstallSuccess(); + } + }, 1000); + }); + + // Проверяем при фокусе окна + window.addEventListener('focus', () => { + setTimeout(() => { + const wasInstalled = this.isInstalled; + this.checkInstallationStatus(); + + if (!wasInstalled && this.isInstalled) { + console.log('✅ PWA installation detected on window focus'); + this.hideInstallPrompts(); + this.showInstallSuccess(); + } + }, 500); + }); } createInstallButton() { - this.installButton = document.createElement('button'); + // Если уже установлено, не создаем кнопку + if (this.isInstalled) { + return; + } + + this.installButton = document.createElement('div'); this.installButton.id = 'pwa-install-button'; this.installButton.className = 'hidden fixed bottom-6 right-6 bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white px-6 py-3 rounded-full shadow-lg transition-all duration-300 z-50 flex items-center space-x-3 group'; @@ -158,17 +197,33 @@ class PWAInstallPrompt { ${buttonText}
+ `; - this.installButton.addEventListener('click', () => { - this.handleInstallClick(); + // Обработчик для установки + this.installButton.addEventListener('click', (e) => { + if (!e.target.classList.contains('close-btn')) { + this.handleInstallClick(); + } + }); + + // Обработчик для закрытия + const closeBtn = this.installButton.querySelector('.close-btn'); + closeBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this.dismissInstallPrompt(); }); document.body.appendChild(this.installButton); } createInstallBanner() { - if (this.installBanner) return; + // Если уже установлено, не создаем баннер + if (this.isInstalled || this.installBanner) { + return; + } this.installBanner = document.createElement('div'); this.installBanner.id = 'pwa-install-banner'; @@ -190,8 +245,8 @@ class PWAInstallPrompt { Install - @@ -204,7 +259,7 @@ class PWAInstallPrompt { if (action === 'install') { this.handleInstallClick(); - } else if (action === 'dismiss') { + } else if (action === 'close') { this.dismissInstallPrompt(); } }); @@ -213,13 +268,9 @@ class PWAInstallPrompt { } showInstallOptions() { - // Дополнительная проверка статуса установки - if (!this.installationChecked) { - this.checkInstallationStatus(); - } - - if (this.isInstalled) { - console.log('💿 App is already installed, not showing install options'); + // Всегда проверяем статус установки перед показом + if (this.checkInstallationStatus()) { + console.log('💿 App is installed, not showing install options'); return; } @@ -233,9 +284,10 @@ class PWAInstallPrompt { } showInstallButton() { - // Дополнительная проверка статуса установки - if (!this.installationChecked) { - this.checkInstallationStatus(); + // Проверяем статус установки + if (this.checkInstallationStatus()) { + console.log('💿 App is installed, not showing install button'); + return; } if (this.installButton && !this.isInstalled) { @@ -256,12 +308,8 @@ class PWAInstallPrompt { } showInstallBanner() { - // Дополнительная проверка статуса установки - if (!this.installationChecked) { - this.checkInstallationStatus(); - } - - if (this.isInstalled) { + // Проверяем статус установки + if (this.checkInstallationStatus()) { console.log('💿 App is installed, not showing install banner'); return; } @@ -287,17 +335,28 @@ class PWAInstallPrompt { if (this.installButton) { this.installButton.classList.add('hidden'); + // Полностью удаляем кнопку если приложение установлено + if (this.isInstalled) { + this.installButton.remove(); + this.installButton = null; + } console.log('💿 Install button hidden'); } if (this.installBanner) { this.installBanner.classList.remove('show'); this.installBanner.style.transform = 'translateY(100%)'; + // Полностью удаляем баннер если приложение установлено + if (this.isInstalled) { + setTimeout(() => { + if (this.installBanner) { + this.installBanner.remove(); + this.installBanner = null; + } + }, 300); + } console.log('💿 Install banner hidden'); } - - // Устанавливаем флаг установки - this.isInstalled = true; } async handleInstallClick() { @@ -320,8 +379,10 @@ class PWAInstallPrompt { if (result.outcome === 'accepted') { console.log('✅ User accepted install prompt'); + this.isInstalled = true; // Устанавливаем флаг сразу this.hideInstallPrompts(); this.saveInstallPreference('accepted', true); + this.saveInstallPreference('installed', true); } else { console.log('❌ User dismissed install prompt'); this.handleInstallDismissal(); @@ -385,34 +446,30 @@ class PWAInstallPrompt { - `; - // Добавляем обработчики событий для кнопок const gotItBtn = modal.querySelector('.got-it-btn'); - const laterBtn = modal.querySelector('.later-btn'); + const closeBtn = modal.querySelector('.close-btn'); gotItBtn.addEventListener('click', () => { modal.remove(); - localStorage.setItem('ios_install_shown', Date.now()); this.saveInstallPreference('ios_instructions_shown', Date.now()); console.log('✅ iOS install instructions acknowledged'); }); - laterBtn.addEventListener('click', () => { + closeBtn.addEventListener('click', () => { modal.remove(); - localStorage.setItem('ios_install_dismissed', Date.now()); this.dismissedCount++; this.saveInstallPreference('dismissed', this.dismissedCount); console.log('❌ iOS install instructions dismissed'); }); document.body.appendChild(modal); - this.saveInstallPreference('ios_instructions_shown', Date.now()); } @@ -451,7 +508,6 @@ class PWAInstallPrompt { `; - // Добавляем обработчик события для кнопки Close const closeBtn = modal.querySelector('.close-btn'); closeBtn.addEventListener('click', () => { modal.remove(); @@ -499,20 +555,9 @@ class PWAInstallPrompt { } shouldShowPrompt() { - // Если приложение уже установлено, не показываем промпт - if (this.isInstalled) { - console.log('💿 App is already installed, not showing install prompt'); - return false; - } - - // Дополнительная проверка статуса установки - if (!this.installationChecked) { - this.checkInstallationStatus(); - } - - // Если после проверки приложение установлено, не показываем промпт - if (this.isInstalled) { - console.log('💿 App installation confirmed, not showing install prompt'); + // Всегда проверяем актуальный статус установки + if (this.checkInstallationStatus()) { + console.log('💿 App is installed, not showing prompt'); return false; } @@ -520,23 +565,19 @@ class PWAInstallPrompt { // Проверяем, не было ли приложение уже установлено if (preferences.installed) { - console.log('💿 Installation preference found, not showing install prompt'); + console.log('💿 Installation preference found, marking as installed'); this.isInstalled = true; + this.hideInstallPrompts(); return false; } if (this.isIOSSafari()) { const lastShown = preferences.ios_instructions_shown; - const lastDismissed = localStorage.getItem('ios_install_dismissed'); if (lastShown && Date.now() - lastShown < 24 * 60 * 60 * 1000) { return false; } - if (lastDismissed && Date.now() - parseInt(lastDismissed) < 7 * 24 * 60 * 60 * 1000) { - return false; - } - return true; } @@ -596,7 +637,6 @@ class PWAInstallPrompt { `; - // Добавляем обработчик события для кнопки OK const okBtn = notification.querySelector('.ok-btn'); okBtn.addEventListener('click', () => { notification.remove(); @@ -622,6 +662,7 @@ class PWAInstallPrompt { try { localStorage.setItem('pwa_install_prefs', JSON.stringify(preferences)); + console.log('💾 Install preference saved:', action, value); } catch (error) { console.warn('⚠️ Could not save install preferences:', error); } @@ -650,6 +691,12 @@ class PWAInstallPrompt { // Public API methods showInstallPrompt() { + // Проверяем статус перед показом + if (this.checkInstallationStatus()) { + console.log('💿 App already installed, not showing prompt'); + return; + } + if (this.isIOSSafari()) { this.showIOSInstallInstructions(); } else if (this.deferredPrompt && !this.isInstalled) { @@ -664,9 +711,12 @@ class PWAInstallPrompt { } getInstallStatus() { + // Проверяем актуальный статус + this.checkInstallationStatus(); + return { isInstalled: this.isInstalled, - canPrompt: !!this.deferredPrompt, + canPrompt: !!this.deferredPrompt && !this.isInstalled, isIOSSafari: this.isIOSSafari(), dismissedCount: this.dismissedCount, shouldShowPrompt: this.shouldShowPrompt() @@ -684,6 +734,21 @@ class PWAInstallPrompt { this.swRegistration = registration; console.log('📡 Service Worker registration set for PWA Install Prompt'); } + + // Метод для принудительной проверки статуса установки + forceInstallationCheck() { + console.log('🔄 Force checking installation status...'); + this.installationChecked = false; + const wasInstalled = this.isInstalled; + const isNowInstalled = this.checkInstallationStatus(); + + if (!wasInstalled && isNowInstalled) { + this.hideInstallPrompts(); + this.showInstallSuccess(); + } + + return isNowInstalled; + } } // Export for module use @@ -700,4 +765,14 @@ if (typeof window !== 'undefined') { window.pwaInstallPrompt = new PWAInstallPrompt(); } }); + + // Дополнительная проверка при полной загрузке страницы + window.addEventListener('load', () => { + if (window.pwaInstallPrompt) { + // Проверяем статус установки через небольшую задержку + setTimeout(() => { + window.pwaInstallPrompt.forceInstallationCheck(); + }, 1000); + } + }); } \ No newline at end of file diff --git a/sw.js b/sw.js index a651630..686b11a 100644 --- a/sw.js +++ b/sw.js @@ -57,6 +57,15 @@ const CACHE_FIRST_PATTERNS = [ /logo/ ]; +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'PWA_INSTALLED') { + self.clients.matchAll().then(clients => { + clients.forEach(client => { + client.postMessage({ type: 'PWA_INSTALL_DETECTED' }); + }); + }); + } +}); // Install event - cache static assets with better error handling self.addEventListener('install', (event) => { console.log('🔧 Service Worker installing...');