2025-08-17 16:04:45 -04:00
|
|
|
class PWAInstallPrompt {
|
|
|
|
|
constructor() {
|
|
|
|
|
this.deferredPrompt = null;
|
|
|
|
|
this.isInstalled = false;
|
|
|
|
|
this.installButton = null;
|
|
|
|
|
this.installBanner = null;
|
|
|
|
|
this.dismissedCount = 0;
|
|
|
|
|
this.maxDismissals = 3;
|
2025-08-23 17:21:32 -04:00
|
|
|
this.installationChecked = false;
|
2025-08-23 17:30:12 -04:00
|
|
|
this.delayedPromptTimeout = null;
|
2025-08-17 16:04:45 -04:00
|
|
|
|
|
|
|
|
this.init();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init() {
|
|
|
|
|
console.log('💿 PWA Install Prompt initializing...');
|
|
|
|
|
|
|
|
|
|
this.checkInstallationStatus();
|
|
|
|
|
this.setupEventListeners();
|
|
|
|
|
this.createInstallButton();
|
|
|
|
|
this.loadInstallPreferences();
|
|
|
|
|
|
2025-08-23 17:21:32 -04:00
|
|
|
// Проверяем статус установки периодически для iOS
|
|
|
|
|
if (this.isIOSSafari()) {
|
|
|
|
|
this.startInstallationMonitoring();
|
2025-08-19 21:54:17 -04:00
|
|
|
}
|
|
|
|
|
|
2025-08-23 17:40:32 -04:00
|
|
|
// Автоматический показ модального окна через 10 секунд для новых пользователей
|
2025-08-23 17:30:12 -04:00
|
|
|
this.scheduleDelayedPrompt();
|
|
|
|
|
|
2025-08-17 16:04:45 -04:00
|
|
|
console.log('✅ PWA Install Prompt initialized');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
checkInstallationStatus() {
|
2025-08-23 17:21:32 -04:00
|
|
|
// Проверяем различные способы определения установки PWA
|
|
|
|
|
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
|
|
|
|
|
const isIOSStandalone = window.navigator.standalone === true;
|
|
|
|
|
const hasInstallPreference = this.loadInstallPreferences().installed;
|
|
|
|
|
|
|
|
|
|
// Проверяем, установлено ли приложение
|
|
|
|
|
if (isStandalone || isIOSStandalone || hasInstallPreference) {
|
2025-08-17 16:04:45 -04:00
|
|
|
this.isInstalled = true;
|
|
|
|
|
console.log('📱 App is already installed as PWA');
|
|
|
|
|
document.body.classList.add('pwa-installed');
|
2025-08-23 17:21:32 -04:00
|
|
|
|
|
|
|
|
// Скрываем все промпты установки
|
|
|
|
|
this.hideInstallPrompts();
|
|
|
|
|
|
|
|
|
|
// Если это iOS, добавляем специальный класс
|
|
|
|
|
if (this.isIOSSafari()) {
|
|
|
|
|
document.body.classList.add('ios-pwa');
|
2025-08-19 21:54:17 -04:00
|
|
|
}
|
2025-08-23 17:21:32 -04:00
|
|
|
|
|
|
|
|
this.installationChecked = true;
|
|
|
|
|
return true;
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
|
|
|
|
|
2025-08-23 17:21:32 -04:00
|
|
|
// Если не установлено, добавляем соответствующие классы
|
|
|
|
|
document.body.classList.add('pwa-browser');
|
2025-08-19 21:54:17 -04:00
|
|
|
|
|
|
|
|
if (this.isIOSSafari()) {
|
|
|
|
|
document.body.classList.add('ios-safari');
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-23 17:21:32 -04:00
|
|
|
this.installationChecked = true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startInstallationMonitoring() {
|
|
|
|
|
// Для iOS Safari мониторим изменения в standalone режиме
|
|
|
|
|
let wasStandalone = window.navigator.standalone;
|
|
|
|
|
|
|
|
|
|
const checkStandalone = () => {
|
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Проверяем каждые 2 секунды
|
|
|
|
|
setInterval(checkStandalone, 2000);
|
|
|
|
|
|
|
|
|
|
// Также проверяем при изменении видимости страницы
|
|
|
|
|
window.addEventListener('visibilitychange', () => {
|
|
|
|
|
if (!document.hidden) {
|
|
|
|
|
setTimeout(checkStandalone, 1000);
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
|
|
|
|
|
2025-08-23 17:30:12 -04:00
|
|
|
scheduleDelayedPrompt() {
|
2025-08-23 17:40:32 -04:00
|
|
|
// Автоматический показ модального окна через 10 секунд для новых пользователей
|
2025-08-23 17:30:12 -04:00
|
|
|
this.delayedPromptTimeout = setTimeout(() => {
|
|
|
|
|
console.log('⏰ Checking if delayed install prompt should be shown...');
|
|
|
|
|
|
|
|
|
|
// Проверяем, нужно ли показывать промпт
|
|
|
|
|
if (!this.isInstalled && this.shouldShowPrompt()) {
|
2025-08-23 17:40:32 -04:00
|
|
|
console.log('💿 Showing delayed install modal after 10 seconds');
|
|
|
|
|
|
|
|
|
|
// Для iOS Safari показываем модальное окно с инструкциями
|
|
|
|
|
if (this.isIOSSafari()) {
|
|
|
|
|
this.showIOSInstallInstructions();
|
|
|
|
|
} else {
|
|
|
|
|
// Для других устройств показываем fallback инструкции
|
|
|
|
|
this.showFallbackInstructions();
|
|
|
|
|
}
|
2025-08-23 17:30:12 -04:00
|
|
|
} else {
|
|
|
|
|
console.log('💿 Delayed install prompt not shown - app is installed or dismissed');
|
|
|
|
|
}
|
|
|
|
|
}, 10000); // 10 секунд
|
|
|
|
|
|
|
|
|
|
console.log('⏰ Delayed install prompt scheduled for 10 seconds');
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-17 16:04:45 -04:00
|
|
|
setupEventListeners() {
|
|
|
|
|
window.addEventListener('beforeinstallprompt', (event) => {
|
|
|
|
|
console.log('💿 Install prompt event captured');
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
this.deferredPrompt = event;
|
|
|
|
|
|
2025-08-23 17:21:32 -04:00
|
|
|
// Показываем промпт только если приложение не установлено
|
2025-08-17 16:04:45 -04:00
|
|
|
if (!this.isInstalled && this.shouldShowPrompt()) {
|
|
|
|
|
this.showInstallOptions();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.addEventListener('appinstalled', () => {
|
|
|
|
|
console.log('✅ PWA installed successfully');
|
|
|
|
|
this.isInstalled = true;
|
|
|
|
|
this.hideInstallPrompts();
|
|
|
|
|
this.showInstallSuccess();
|
|
|
|
|
this.saveInstallPreference('installed', true);
|
|
|
|
|
|
|
|
|
|
document.body.classList.remove('pwa-browser');
|
|
|
|
|
document.body.classList.add('pwa-installed');
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-23 17:21:32 -04:00
|
|
|
// Дополнительная проверка для iOS
|
2025-08-17 16:04:45 -04:00
|
|
|
if (this.isIOSSafari()) {
|
2025-08-19 21:54:17 -04:00
|
|
|
let wasStandalone = window.navigator.standalone;
|
|
|
|
|
|
2025-08-17 16:04:45 -04:00
|
|
|
window.addEventListener('visibilitychange', () => {
|
|
|
|
|
if (document.hidden) return;
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
2025-08-19 21:54:17 -04:00
|
|
|
const isStandalone = window.navigator.standalone;
|
|
|
|
|
|
|
|
|
|
if (isStandalone && !wasStandalone && !this.isInstalled) {
|
|
|
|
|
console.log('✅ iOS PWA installation detected');
|
2025-08-17 16:04:45 -04:00
|
|
|
this.isInstalled = true;
|
|
|
|
|
this.hideInstallPrompts();
|
|
|
|
|
this.showInstallSuccess();
|
2025-08-19 21:54:17 -04:00
|
|
|
document.body.classList.remove('pwa-browser');
|
|
|
|
|
document.body.classList.add('pwa-installed', 'ios-pwa');
|
2025-08-23 17:21:32 -04:00
|
|
|
|
|
|
|
|
// Сохраняем предпочтение установки
|
|
|
|
|
this.saveInstallPreference('installed', true);
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
2025-08-19 21:54:17 -04:00
|
|
|
|
|
|
|
|
wasStandalone = isStandalone;
|
2025-08-17 16:04:45 -04:00
|
|
|
}, 1000);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createInstallButton() {
|
|
|
|
|
this.installButton = document.createElement('button');
|
|
|
|
|
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';
|
2025-08-19 21:54:17 -04:00
|
|
|
|
|
|
|
|
const buttonText = this.isIOSSafari() ? 'Install App' : 'Install App';
|
|
|
|
|
const buttonIcon = this.isIOSSafari() ? 'fas fa-share' : 'fas fa-download';
|
|
|
|
|
|
2025-08-17 16:04:45 -04:00
|
|
|
this.installButton.innerHTML = `
|
2025-08-19 21:54:17 -04:00
|
|
|
<i class="${buttonIcon} transition-transform group-hover:scale-110"></i>
|
|
|
|
|
<span class="font-medium">${buttonText}</span>
|
2025-08-17 16:04:45 -04:00
|
|
|
<div class="absolute -top-1 -right-1 w-3 h-3 bg-green-400 rounded-full animate-pulse"></div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
this.installButton.addEventListener('click', () => {
|
|
|
|
|
this.handleInstallClick();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.body.appendChild(this.installButton);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createInstallBanner() {
|
|
|
|
|
if (this.installBanner) return;
|
|
|
|
|
|
|
|
|
|
this.installBanner = document.createElement('div');
|
|
|
|
|
this.installBanner.id = 'pwa-install-banner';
|
|
|
|
|
this.installBanner.className = 'pwa-install-banner fixed bottom-0 left-0 right-0 transform translate-y-full transition-transform duration-300 z-40';
|
|
|
|
|
this.installBanner.innerHTML = `
|
2025-08-19 21:54:17 -04:00
|
|
|
<div class="bg-gray-800/95 backdrop-blur-sm border-t border-gray-600/30 p-4">
|
|
|
|
|
<div class="max-w-4xl mx-auto flex items-center justify-between">
|
|
|
|
|
<div class="flex items-center space-x-4">
|
|
|
|
|
<div class="w-12 h-12 bg-orange-500/10 border border-orange-500/20 rounded-lg flex items-center justify-center">
|
|
|
|
|
<i class="fas fa-shield-halved text-orange-400 text-xl"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="font-medium text-white">Install SecureBit.chat</div>
|
|
|
|
|
<div class="text-sm text-gray-300">Get the native app experience with enhanced security</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center space-x-3">
|
|
|
|
|
<button class="install-btn bg-orange-500 hover:bg-orange-600 text-white px-4 py-2 rounded-lg font-medium transition-colors" data-action="install">
|
|
|
|
|
<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>
|
|
|
|
|
</div>
|
2025-08-17 16:04:45 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
// Handle banner actions
|
|
|
|
|
this.installBanner.addEventListener('click', (event) => {
|
|
|
|
|
const action = event.target.closest('[data-action]')?.dataset.action;
|
|
|
|
|
|
|
|
|
|
if (action === 'install') {
|
|
|
|
|
this.handleInstallClick();
|
|
|
|
|
} else if (action === 'dismiss') {
|
|
|
|
|
this.dismissInstallPrompt();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.body.appendChild(this.installBanner);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showInstallOptions() {
|
2025-08-23 17:21:32 -04:00
|
|
|
// Дополнительная проверка статуса установки
|
|
|
|
|
if (!this.installationChecked) {
|
|
|
|
|
this.checkInstallationStatus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.isInstalled) {
|
|
|
|
|
console.log('💿 App is already installed, not showing install options');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-08-17 16:04:45 -04:00
|
|
|
|
2025-08-23 17:30:12 -04:00
|
|
|
// Отменяем отложенный промпт, так как показываем промпт сейчас
|
|
|
|
|
if (this.delayedPromptTimeout) {
|
|
|
|
|
clearTimeout(this.delayedPromptTimeout);
|
|
|
|
|
this.delayedPromptTimeout = null;
|
|
|
|
|
console.log('⏰ Delayed install prompt cancelled - showing prompt now');
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-23 17:40:32 -04:00
|
|
|
// Для мобильных устройств показываем модальные окна, а не кнопки
|
2025-08-19 21:54:17 -04:00
|
|
|
if (this.isIOSSafari()) {
|
2025-08-23 17:40:32 -04:00
|
|
|
this.showIOSInstallInstructions();
|
2025-08-19 21:54:17 -04:00
|
|
|
} else if (this.isMobileDevice()) {
|
2025-08-23 17:40:32 -04:00
|
|
|
// Для мобильных показываем fallback инструкции вместо баннера
|
|
|
|
|
this.showFallbackInstructions();
|
2025-08-17 16:04:45 -04:00
|
|
|
} else {
|
2025-08-23 17:40:32 -04:00
|
|
|
// Для десктопа показываем кнопку
|
2025-08-17 16:04:45 -04:00
|
|
|
this.showInstallButton();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showInstallButton() {
|
2025-08-23 17:21:32 -04:00
|
|
|
// Дополнительная проверка статуса установки
|
|
|
|
|
if (!this.installationChecked) {
|
|
|
|
|
this.checkInstallationStatus();
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-17 16:04:45 -04:00
|
|
|
if (this.installButton && !this.isInstalled) {
|
|
|
|
|
this.installButton.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
// Add entrance animation
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.installButton.style.transform = 'scale(1.1)';
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.installButton.style.transform = 'scale(1)';
|
|
|
|
|
}, 200);
|
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
|
|
console.log('💿 Install button shown');
|
2025-08-23 17:21:32 -04:00
|
|
|
} else {
|
|
|
|
|
console.log('💿 Install button not shown - app is installed or button not available');
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showInstallBanner() {
|
2025-08-23 17:21:32 -04:00
|
|
|
// Дополнительная проверка статуса установки
|
|
|
|
|
if (!this.installationChecked) {
|
|
|
|
|
this.checkInstallationStatus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.isInstalled) {
|
|
|
|
|
console.log('💿 App is installed, not showing install banner');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-17 16:04:45 -04:00
|
|
|
if (!this.installBanner) {
|
|
|
|
|
this.createInstallBanner();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.installBanner && !this.isInstalled) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.installBanner.classList.add('show');
|
2025-08-19 21:54:17 -04:00
|
|
|
this.installBanner.style.transform = 'translateY(0)';
|
2025-08-17 16:04:45 -04:00
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
console.log('💿 Install banner shown');
|
2025-08-23 17:21:32 -04:00
|
|
|
} else {
|
|
|
|
|
console.log('💿 Install banner not shown - app is installed or banner not available');
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hideInstallPrompts() {
|
2025-08-23 17:21:32 -04:00
|
|
|
console.log('💿 Hiding all install prompts');
|
|
|
|
|
|
2025-08-23 17:30:12 -04:00
|
|
|
// Отменяем отложенный промпт
|
|
|
|
|
if (this.delayedPromptTimeout) {
|
|
|
|
|
clearTimeout(this.delayedPromptTimeout);
|
|
|
|
|
this.delayedPromptTimeout = null;
|
|
|
|
|
console.log('⏰ Delayed install prompt cancelled');
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-17 16:04:45 -04:00
|
|
|
if (this.installButton) {
|
|
|
|
|
this.installButton.classList.add('hidden');
|
2025-08-23 17:21:32 -04:00
|
|
|
console.log('💿 Install button hidden');
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.installBanner) {
|
|
|
|
|
this.installBanner.classList.remove('show');
|
2025-08-19 21:54:17 -04:00
|
|
|
this.installBanner.style.transform = 'translateY(100%)';
|
2025-08-23 17:21:32 -04:00
|
|
|
console.log('💿 Install banner hidden');
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
2025-08-23 17:21:32 -04:00
|
|
|
|
|
|
|
|
// Устанавливаем флаг установки
|
|
|
|
|
this.isInstalled = true;
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async handleInstallClick() {
|
|
|
|
|
if (this.isIOSSafari()) {
|
2025-08-19 21:54:17 -04:00
|
|
|
this.showIOSInstallInstructions();
|
2025-08-17 16:04:45 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!this.deferredPrompt) {
|
|
|
|
|
console.warn('⚠️ Install prompt not available');
|
|
|
|
|
this.showFallbackInstructions();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
console.log('💿 Showing install prompt...');
|
|
|
|
|
|
|
|
|
|
const result = await this.deferredPrompt.prompt();
|
|
|
|
|
console.log('💿 Install prompt result:', result.outcome);
|
|
|
|
|
|
|
|
|
|
if (result.outcome === 'accepted') {
|
|
|
|
|
console.log('✅ User accepted install prompt');
|
|
|
|
|
this.hideInstallPrompts();
|
|
|
|
|
this.saveInstallPreference('accepted', true);
|
|
|
|
|
} else {
|
|
|
|
|
console.log('❌ User dismissed install prompt');
|
|
|
|
|
this.handleInstallDismissal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.deferredPrompt = null;
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('❌ Install prompt failed:', error);
|
|
|
|
|
this.showFallbackInstructions();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-19 21:54:17 -04:00
|
|
|
showIOSInstallInstructions() {
|
2025-08-17 16:04:45 -04:00
|
|
|
const modal = document.createElement('div');
|
2025-08-19 21:54:17 -04:00
|
|
|
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 backdrop-blur-sm';
|
2025-08-17 16:04:45 -04:00
|
|
|
modal.innerHTML = `
|
|
|
|
|
<div class="bg-gray-800 rounded-xl p-6 max-w-sm w-full text-center">
|
|
|
|
|
<div class="w-16 h-16 bg-blue-500/10 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
<i class="fab fa-apple text-blue-400 text-2xl"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<h3 class="text-xl font-semibold text-white mb-4">Install on iOS</h3>
|
2025-08-19 21:54:17 -04:00
|
|
|
|
|
|
|
|
<div class="space-y-4 text-left text-sm text-gray-300 mb-6">
|
|
|
|
|
<div class="flex items-start space-x-3">
|
|
|
|
|
<div class="w-8 h-8 bg-blue-500 rounded-full text-white flex items-center justify-center text-sm font-bold flex-shrink-0 mt-0.5">1</div>
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<div class="font-medium text-white mb-1">Tap the Share button</div>
|
|
|
|
|
<div class="flex items-center text-blue-400">
|
|
|
|
|
<i class="fas fa-share mr-2"></i>
|
|
|
|
|
<span>Usually at the bottom of Safari</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-17 16:04:45 -04:00
|
|
|
</div>
|
2025-08-19 21:54:17 -04:00
|
|
|
|
|
|
|
|
<div class="flex items-start space-x-3">
|
|
|
|
|
<div class="w-8 h-8 bg-blue-500 rounded-full text-white flex items-center justify-center text-sm font-bold flex-shrink-0 mt-0.5">2</div>
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<div class="font-medium text-white mb-1">Find "Add to Home Screen"</div>
|
|
|
|
|
<div class="text-gray-400">Scroll down in the share menu</div>
|
|
|
|
|
</div>
|
2025-08-17 16:04:45 -04:00
|
|
|
</div>
|
2025-08-19 21:54:17 -04:00
|
|
|
|
|
|
|
|
<div class="flex items-start space-x-3">
|
|
|
|
|
<div class="w-8 h-8 bg-blue-500 rounded-full text-white flex items-center justify-center text-sm font-bold flex-shrink-0 mt-0.5">3</div>
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<div class="font-medium text-white mb-1">Tap "Add"</div>
|
|
|
|
|
<div class="text-gray-400">Confirm to install SecureBit.chat</div>
|
|
|
|
|
</div>
|
2025-08-17 16:04:45 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-19 21:54:17 -04:00
|
|
|
|
|
|
|
|
<div class="bg-orange-500/10 border border-orange-500/20 rounded-lg p-3 mb-4">
|
|
|
|
|
<p class="text-orange-300 text-xs">
|
|
|
|
|
<i class="fas fa-info-circle mr-1"></i>
|
|
|
|
|
After installation, open SecureBit from your home screen for the best experience.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="flex space-x-3">
|
2025-08-23 17:40:32 -04:00
|
|
|
<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">
|
2025-08-19 21:54:17 -04:00
|
|
|
Got it
|
|
|
|
|
</button>
|
2025-08-23 17:40:32 -04:00
|
|
|
<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">
|
2025-08-19 21:54:17 -04:00
|
|
|
Later
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2025-08-17 16:04:45 -04:00
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
2025-08-23 17:40:32 -04:00
|
|
|
// Добавляем обработчики событий для кнопок
|
|
|
|
|
const gotItBtn = modal.querySelector('.got-it-btn');
|
|
|
|
|
const laterBtn = modal.querySelector('.later-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', () => {
|
|
|
|
|
modal.remove();
|
|
|
|
|
localStorage.setItem('ios_install_dismissed', Date.now());
|
|
|
|
|
this.dismissedCount++;
|
|
|
|
|
this.saveInstallPreference('dismissed', this.dismissedCount);
|
|
|
|
|
console.log('❌ iOS install instructions dismissed');
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-17 16:04:45 -04:00
|
|
|
document.body.appendChild(modal);
|
2025-08-19 21:54:17 -04:00
|
|
|
|
|
|
|
|
this.saveInstallPreference('ios_instructions_shown', Date.now());
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showFallbackInstructions() {
|
|
|
|
|
const modal = document.createElement('div');
|
2025-08-19 21:54:17 -04:00
|
|
|
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 backdrop-blur-sm';
|
2025-08-17 16:04:45 -04:00
|
|
|
modal.innerHTML = `
|
|
|
|
|
<div class="bg-gray-800 rounded-xl p-6 max-w-md w-full text-center">
|
|
|
|
|
<div class="w-16 h-16 bg-orange-500/10 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
<i class="fas fa-download text-orange-400 text-2xl"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<h3 class="text-xl font-semibold text-white mb-4">Install SecureBit.chat</h3>
|
|
|
|
|
<p class="text-gray-300 text-sm mb-6 leading-relaxed">
|
|
|
|
|
To install this app, look for the install option in your browser menu or address bar.
|
|
|
|
|
Different browsers have different install methods.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-3 text-left text-sm">
|
|
|
|
|
<div class="bg-gray-700/50 rounded-lg p-3">
|
|
|
|
|
<div class="font-medium text-white mb-1">Chrome/Edge</div>
|
|
|
|
|
<div class="text-gray-400">Look for install icon in address bar</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="bg-gray-700/50 rounded-lg p-3">
|
|
|
|
|
<div class="font-medium text-white mb-1">Firefox</div>
|
|
|
|
|
<div class="text-gray-400">Add bookmark to home screen</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="bg-gray-700/50 rounded-lg p-3">
|
|
|
|
|
<div class="font-medium text-white mb-1">Safari</div>
|
|
|
|
|
<div class="text-gray-400">Share → Add to Home Screen</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-08-23 17:40:32 -04:00
|
|
|
<button class="close-btn w-full bg-orange-500 hover:bg-orange-600 text-white py-3 px-4 rounded-lg font-medium transition-colors mt-6">
|
2025-08-17 16:04:45 -04:00
|
|
|
Close
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
2025-08-23 17:40:32 -04:00
|
|
|
// Добавляем обработчик события для кнопки Close
|
|
|
|
|
const closeBtn = modal.querySelector('.close-btn');
|
|
|
|
|
closeBtn.addEventListener('click', () => {
|
|
|
|
|
modal.remove();
|
|
|
|
|
console.log('📱 Fallback install instructions closed');
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-17 16:04:45 -04:00
|
|
|
document.body.appendChild(modal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showInstallSuccess() {
|
2025-08-23 17:21:32 -04:00
|
|
|
console.log('✅ Showing installation success notification');
|
|
|
|
|
|
2025-08-17 16:04:45 -04:00
|
|
|
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 transform translate-x-full transition-transform duration-300';
|
2025-08-19 21:54:17 -04:00
|
|
|
|
|
|
|
|
const successText = this.isIOSSafari() ?
|
|
|
|
|
'iOS App installed! Open from home screen.' :
|
|
|
|
|
'SecureBit.chat is now on your device';
|
|
|
|
|
|
2025-08-17 16:04:45 -04:00
|
|
|
notification.innerHTML = `
|
|
|
|
|
<div class="flex items-center space-x-3">
|
|
|
|
|
<div class="w-8 h-8 bg-white/20 rounded-full flex items-center justify-center">
|
|
|
|
|
<i class="fas fa-check text-lg"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="font-medium">App Installed!</div>
|
2025-08-19 21:54:17 -04:00
|
|
|
<div class="text-sm opacity-90">${successText}</div>
|
2025-08-17 16:04:45 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
notification.classList.remove('translate-x-full');
|
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
notification.classList.add('translate-x-full');
|
|
|
|
|
setTimeout(() => notification.remove(), 300);
|
2025-08-19 21:54:17 -04:00
|
|
|
}, 5000);
|
2025-08-23 17:21:32 -04:00
|
|
|
|
|
|
|
|
// Скрываем все промпты установки
|
|
|
|
|
this.hideInstallPrompts();
|
2025-08-19 21:54:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shouldShowPrompt() {
|
2025-08-23 17:21:32 -04:00
|
|
|
// Если приложение уже установлено, не показываем промпт
|
|
|
|
|
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');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-19 21:54:17 -04:00
|
|
|
const preferences = this.loadInstallPreferences();
|
|
|
|
|
|
2025-08-23 17:21:32 -04:00
|
|
|
// Проверяем, не было ли приложение уже установлено
|
|
|
|
|
if (preferences.installed) {
|
|
|
|
|
console.log('💿 Installation preference found, not showing install prompt');
|
|
|
|
|
this.isInstalled = true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-08-19 21:54:17 -04:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (preferences.dismissed >= this.maxDismissals) return false;
|
|
|
|
|
|
|
|
|
|
const lastDismissed = preferences.lastDismissed;
|
|
|
|
|
if (lastDismissed && Date.now() - lastDismissed < 24 * 60 * 60 * 1000) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dismissInstallPrompt() {
|
|
|
|
|
this.dismissedCount++;
|
|
|
|
|
this.hideInstallPrompts();
|
|
|
|
|
this.saveInstallPreference('dismissed', this.dismissedCount);
|
|
|
|
|
|
|
|
|
|
console.log(`💿 Install prompt dismissed (${this.dismissedCount}/${this.maxDismissals})`);
|
|
|
|
|
|
|
|
|
|
// Show encouraging message on final dismissal
|
|
|
|
|
if (this.dismissedCount >= this.maxDismissals) {
|
|
|
|
|
this.showFinalDismissalMessage();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleInstallDismissal() {
|
|
|
|
|
this.dismissedCount++;
|
|
|
|
|
this.saveInstallPreference('dismissed', this.dismissedCount);
|
|
|
|
|
|
|
|
|
|
if (this.dismissedCount < this.maxDismissals) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (!this.isInstalled && this.shouldShowPrompt()) {
|
|
|
|
|
this.showInstallButton();
|
|
|
|
|
}
|
2025-08-19 21:54:17 -04:00
|
|
|
}, 300000);
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showFinalDismissalMessage() {
|
|
|
|
|
const notification = document.createElement('div');
|
|
|
|
|
notification.className = 'fixed bottom-4 left-4 right-4 bg-blue-500/90 text-white p-4 rounded-lg shadow-lg z-50 backdrop-blur-sm';
|
|
|
|
|
notification.innerHTML = `
|
|
|
|
|
<div class="flex items-start space-x-3">
|
|
|
|
|
<div class="w-8 h-8 bg-white/20 rounded-full flex items-center justify-center flex-shrink-0">
|
|
|
|
|
<i class="fas fa-info text-sm"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<div class="font-medium mb-1">Install Anytime</div>
|
|
|
|
|
<div class="text-sm opacity-90 mb-3">
|
|
|
|
|
You can still install SecureBit.chat from your browser's menu for the best experience.
|
|
|
|
|
</div>
|
|
|
|
|
<button onclick="this.parentElement.parentElement.remove()"
|
|
|
|
|
class="text-sm bg-white/20 hover:bg-white/30 px-3 py-1 rounded transition-colors">
|
|
|
|
|
OK
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (notification.parentElement) {
|
|
|
|
|
notification.remove();
|
|
|
|
|
}
|
|
|
|
|
}, 10000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saveInstallPreference(action, value) {
|
|
|
|
|
const preferences = this.loadInstallPreferences();
|
|
|
|
|
preferences[action] = value;
|
|
|
|
|
|
|
|
|
|
if (action === 'dismissed') {
|
|
|
|
|
preferences.lastDismissed = Date.now();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
localStorage.setItem('pwa_install_prefs', JSON.stringify(preferences));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('⚠️ Could not save install preferences:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadInstallPreferences() {
|
|
|
|
|
try {
|
|
|
|
|
const saved = localStorage.getItem('pwa_install_prefs');
|
|
|
|
|
return saved ? JSON.parse(saved) : { dismissed: 0, installed: false };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('⚠️ Could not load install preferences:', error);
|
|
|
|
|
return { dismissed: 0, installed: false };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isMobileDevice() {
|
|
|
|
|
return /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isIOSSafari() {
|
|
|
|
|
const userAgent = navigator.userAgent;
|
2025-08-19 21:54:17 -04:00
|
|
|
const isIOS = /iPad|iPhone|iPod/.test(userAgent);
|
|
|
|
|
const isSafari = /Safari/.test(userAgent) && !/CriOS|FxiOS|EdgiOS/.test(userAgent);
|
|
|
|
|
return isIOS && isSafari;
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Public API methods
|
|
|
|
|
showInstallPrompt() {
|
2025-08-19 21:54:17 -04:00
|
|
|
if (this.isIOSSafari()) {
|
|
|
|
|
this.showIOSInstallInstructions();
|
|
|
|
|
} else if (this.deferredPrompt && !this.isInstalled) {
|
2025-08-17 16:04:45 -04:00
|
|
|
this.handleInstallClick();
|
|
|
|
|
} else {
|
|
|
|
|
this.showFallbackInstructions();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hideInstallPrompt() {
|
|
|
|
|
this.hideInstallPrompts();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getInstallStatus() {
|
|
|
|
|
return {
|
|
|
|
|
isInstalled: this.isInstalled,
|
|
|
|
|
canPrompt: !!this.deferredPrompt,
|
2025-08-19 21:54:17 -04:00
|
|
|
isIOSSafari: this.isIOSSafari(),
|
2025-08-17 16:04:45 -04:00
|
|
|
dismissedCount: this.dismissedCount,
|
|
|
|
|
shouldShowPrompt: this.shouldShowPrompt()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resetDismissals() {
|
|
|
|
|
this.dismissedCount = 0;
|
|
|
|
|
this.saveInstallPreference('dismissed', 0);
|
|
|
|
|
console.log('💿 Install dismissals reset');
|
|
|
|
|
}
|
2025-08-19 21:54:17 -04:00
|
|
|
|
|
|
|
|
// Method for setting service worker registration
|
|
|
|
|
setServiceWorkerRegistration(registration) {
|
|
|
|
|
this.swRegistration = registration;
|
|
|
|
|
console.log('📡 Service Worker registration set for PWA Install Prompt');
|
|
|
|
|
}
|
2025-08-17 16:04:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Export for module use
|
|
|
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
|
|
|
module.exports = PWAInstallPrompt;
|
|
|
|
|
} else {
|
|
|
|
|
window.PWAInstallPrompt = PWAInstallPrompt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Auto-initialize
|
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
if (!window.pwaInstallPrompt) {
|
|
|
|
|
window.pwaInstallPrompt = new PWAInstallPrompt();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|