fix: prevent install prompt showing in installed PWA

- Improve installation status detection logic
- Add proper DOM cleanup when PWA is installed
- Enhance monitoring for installation state changes
- Fix shouldShowPrompt() logic to always check current status
- Add forceInstallationCheck() method for manual status updates
This commit is contained in:
lockbitchat
2025-08-24 17:04:01 -04:00
parent 171a7d9dfb
commit 26ba6eebb9
2 changed files with 168 additions and 84 deletions

View File

@@ -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 {
<i class="${buttonIcon} transition-transform group-hover:scale-110"></i>
<span class="font-medium">${buttonText}</span>
<div class="absolute -top-1 -right-1 w-3 h-3 bg-green-400 rounded-full animate-pulse"></div>
<button class="close-btn absolute -top-2 -left-2 w-6 h-6 bg-red-500 hover:bg-red-600 rounded-full text-white text-xs flex items-center justify-center transition-colors">
×
</button>
`;
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 {
<i class="fas fa-download mr-2"></i>
Install
</button>
<button class="dismiss-btn text-gray-400 hover:text-white px-3 py-2 rounded-lg transition-colors" data-action="dismiss">
Later
<button class="close-btn text-gray-400 hover:text-white px-3 py-2 rounded-lg transition-colors" data-action="close">
<i class="fas fa-times"></i>
</button>
</div>
</div>
@@ -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 {
<button class="got-it-btn flex-1 bg-blue-500 hover:bg-blue-600 text-white py-3 px-4 rounded-lg font-medium transition-colors">
Got it
</button>
<button class="later-btn flex-1 bg-gray-600 hover:bg-gray-500 text-white py-3 px-4 rounded-lg font-medium transition-colors">
Later
<button class="close-btn 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>
`;
// Добавляем обработчики событий для кнопок
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 {
</div>
`;
// Добавляем обработчик события для кнопки 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 {
</div>
`;
// Добавляем обработчик события для кнопки 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);
}
});
}