// PWA Service Worker Registration и установка class PWAManager { constructor() { this.deferredPrompt = null; this.init(); } init() { console.log('PWA Manager инициализирован'); this.registerServiceWorker(); this.setupInstallPrompt(); this.setupAppInstalled(); this.checkPWARequirements(); this.setupServiceWorkerMessages(); } // Проверка требований PWA checkPWARequirements() { console.log('Проверка требований PWA:'); console.log('- Service Worker:', 'serviceWorker' in navigator); console.log('- HTTPS:', location.protocol === 'https:' || location.hostname === 'localhost'); console.log('- Manifest:', document.querySelector('link[rel="manifest"]') !== null); console.log('- Icons:', document.querySelector('link[rel="icon"]') !== null); } // Регистрация Service Worker registerServiceWorker() { if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') .then((registration) => { console.log('SW зарегистрирован успешно:', registration.scope); // Проверяем обновления registration.addEventListener('updatefound', () => { const newWorker = registration.installing; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { this.showUpdateNotification(); } }); }); }) .catch((error) => { console.log('Ошибка регистрации SW:', error); }); }); } else { console.log('Service Worker не поддерживается'); } } // Показ уведомления об обновлении showUpdateNotification() { if (confirm('Доступна новая версия приложения. Обновить?')) { if (navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: 'SKIP_WAITING' }); } window.location.reload(); } } // Настройка промпта установки setupInstallPrompt() { window.addEventListener('beforeinstallprompt', (e) => { console.log('beforeinstallprompt событие получено'); e.preventDefault(); this.deferredPrompt = e; this.showInstallButton(); }); } // Показ кнопки установки showInstallButton() { console.log('Попытка показать кнопку установки'); // Проверяем, не установлено ли уже приложение if (this.isPWAInstalled()) { console.log('Приложение уже установлено'); return; } // Показываем кнопку только на мобильных устройствах if (!this.isMobileDevice()) { console.log('Кнопка установки скрыта для ПК версии'); return; } // Проверяем, поддерживает ли браузер установку PWA if (!this.deferredPrompt && !this.isMobileSafari()) { console.log('Установка PWA не поддерживается в этом браузере'); return; } const installButton = this.createInstallButton(); this.addInstallButtonToPage(installButton); } // Проверка на мобильное устройство isMobileDevice() { const ua = navigator.userAgent; return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua) || (navigator.maxTouchPoints && navigator.maxTouchPoints > 2) || window.matchMedia('(max-width: 768px)').matches; } // Проверка на мобильный Safari isMobileSafari() { const ua = navigator.userAgent; return /iPad|iPhone|iPod/.test(ua) && /Safari/.test(ua) && !/CriOS|FxiOS|OPiOS|mercury/.test(ua); } // Создание кнопки установки createInstallButton() { const installButton = document.createElement('button'); // Разный текст для разных браузеров if (this.isMobileSafari()) { installButton.textContent = '📱 Добавить на главный экран'; } else { installButton.textContent = '📱 Установить приложение'; } installButton.className = 'btnSave'; installButton.style.marginTop = '10px'; installButton.style.width = '100%'; installButton.style.fontSize = '14px'; installButton.id = 'pwa-install-button'; installButton.addEventListener('click', () => { this.installApp(); }); return installButton; } // Добавление кнопки на страницу addInstallButtonToPage(installButton) { // Удаляем существующую кнопку, если есть const existingButton = document.getElementById('pwa-install-button'); if (existingButton) { existingButton.remove(); } // Ищем подходящее место для кнопки const authLink = document.querySelector('.auth-link'); const footer = document.querySelector('.footer'); const container = document.querySelector('.container'); if (authLink) { authLink.appendChild(installButton); console.log('Кнопка установки добавлена в auth-link'); } else if (footer) { footer.insertBefore(installButton, footer.firstChild); console.log('Кнопка установки добавлена в footer'); } else if (container) { container.appendChild(installButton); console.log('Кнопка установки добавлена в container'); } else { document.body.appendChild(installButton); console.log('Кнопка установки добавлена в body'); } } // Установка приложения installApp() { console.log('Попытка установки приложения'); if (this.isMobileSafari()) { // Для iOS Safari показываем инструкции this.showSafariInstructions(); return; } if (this.deferredPrompt) { this.deferredPrompt.prompt(); this.deferredPrompt.userChoice.then((choiceResult) => { console.log('Результат установки:', choiceResult.outcome); if (choiceResult.outcome === 'accepted') { console.log('Пользователь установил приложение'); } this.deferredPrompt = null; this.removeInstallButton(); }); } else { console.log('deferredPrompt не доступен'); this.showManualInstallInstructions(); } } // Показать инструкции для Safari showSafariInstructions() { const instructions = ` Для установки приложения на iOS: 1. Нажмите кнопку "Поделиться" (□↗) внизу экрана 2. Выберите "На экран Домой" 3. Нажмите "Добавить" `; alert(instructions); } // Показать инструкции для ручной установки showManualInstallInstructions() { const instructions = ` Для установки приложения: 1. Откройте меню браузера (⋮ или ☰) 2. Найдите "Установить приложение" или "Добавить на главный экран" 3. Следуйте инструкциям браузера `; alert(instructions); } // Удаление кнопки установки removeInstallButton() { const installButton = document.getElementById('pwa-install-button'); if (installButton) { installButton.remove(); console.log('Кнопка установки удалена'); } } // Обработка успешной установки setupAppInstalled() { window.addEventListener('appinstalled', () => { console.log('PWA установлено успешно'); this.removeInstallButton(); }); } // Проверка статуса PWA isPWAInstalled() { return window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true || document.referrer.includes('android-app://') || window.matchMedia('(display-mode: fullscreen)').matches; } // Получение информации о PWA getPWAInfo() { return { isInstalled: this.isPWAInstalled(), isOnline: navigator.onLine, hasServiceWorker: 'serviceWorker' in navigator, userAgent: navigator.userAgent, hasDeferredPrompt: this.deferredPrompt !== null, isMobileDevice: this.isMobileDevice(), isMobileSafari: this.isMobileSafari(), platform: navigator.platform, language: navigator.language, displayMode: window.matchMedia('(display-mode: standalone)').matches ? 'standalone' : 'browser' }; } // Принудительное обновление кэша async forceUpdateCache() { console.log('Принудительное обновление кэша...'); if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { try { // Отправляем сообщение Service Worker для обновления кэша navigator.serviceWorker.controller.postMessage({ type: 'FORCE_UPDATE_CACHE' }); console.log('Запрос на обновление кэша отправлен'); return true; } catch (error) { console.error('Ошибка при обновлении кэша:', error); return false; } } else { console.log('Service Worker не доступен'); return false; } } // Полная очистка кэша async clearAllCache() { console.log('Полная очистка кэша...'); if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { try { // Отправляем сообщение Service Worker для очистки кэша navigator.serviceWorker.controller.postMessage({ type: 'CLEAR_ALL_CACHE' }); console.log('Запрос на очистку кэша отправлен'); return true; } catch (error) { console.error('Ошибка при очистке кэша:', error); return false; } } else { console.log('Service Worker не доступен'); return false; } } // Получение версии кэша async getCacheVersion() { return new Promise((resolve) => { if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { const messageChannel = new MessageChannel(); messageChannel.port1.onmessage = (event) => { resolve(event.data.version || 'Неизвестно'); }; navigator.serviceWorker.controller.postMessage( { type: 'GET_VERSION' }, [messageChannel.port2] ); } else { resolve('Service Worker не доступен'); } }); } // Проверка обновлений и принудительное обновление async checkForUpdates() { console.log('Проверка обновлений...'); if ('serviceWorker' in navigator) { try { const registration = await navigator.serviceWorker.getRegistration(); if (registration) { await registration.update(); console.log('Проверка обновлений завершена'); return true; } } catch (error) { console.error('Ошибка при проверке обновлений:', error); return false; } } return false; } // Настройка обработки сообщений от Service Worker setupServiceWorkerMessages() { if ('serviceWorker' in navigator) { navigator.serviceWorker.addEventListener('message', (event) => { console.log('Получено сообщение от SW:', event.data); switch (event.data.type) { case 'CACHE_UPDATED': console.log('Кэш обновлен до версии:', event.data.version); this.showNotification('Кэш успешно обновлен!', 'success'); break; case 'CACHE_CLEARED': console.log('Кэш полностью очищен'); this.showNotification('Кэш полностью очищен!', 'info'); break; } }); } } // Показ уведомления showNotification(message, type = 'info') { // Создаем уведомление const notification = document.createElement('div'); notification.className = `pwa-notification pwa-notification-${type}`; notification.textContent = message; // Стили для уведомления notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 12px 20px; border-radius: 8px; color: white; font-weight: bold; z-index: 10000; max-width: 300px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); transform: translateX(100%); transition: transform 0.3s ease; `; // Цвета для разных типов уведомлений switch (type) { case 'success': notification.style.backgroundColor = '#28a745'; break; case 'error': notification.style.backgroundColor = '#dc3545'; break; case 'warning': notification.style.backgroundColor = '#ffc107'; notification.style.color = '#000'; break; default: notification.style.backgroundColor = '#007bff'; } // Добавляем на страницу document.body.appendChild(notification); // Анимация появления setTimeout(() => { notification.style.transform = 'translateX(0)'; }, 100); // Автоматическое удаление через 3 секунды setTimeout(() => { notification.style.transform = 'translateX(100%)'; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); }, 3000); } } // Инициализация PWA Manager const pwaManager = new PWAManager(); // Экспорт для использования в других скриптах window.PWAManager = pwaManager; // Добавляем глобальные функции для управления кэшем window.debugPWA = () => { console.log('PWA Debug Info:', pwaManager.getPWAInfo()); }; // Принудительное обновление кэша window.updateCache = () => { return pwaManager.forceUpdateCache(); }; // Полная очистка кэша window.clearCache = () => { return pwaManager.clearAllCache(); }; // Получение версии кэша window.getCacheVersion = () => { return pwaManager.getCacheVersion(); }; // Проверка обновлений window.checkUpdates = () => { return pwaManager.checkForUpdates(); }; // Комбинированная функция: проверка обновлений + принудительное обновление кэша window.forceUpdate = async () => { console.log('Принудительное обновление приложения...'); // Сначала проверяем обновления await pwaManager.checkForUpdates(); // Затем принудительно обновляем кэш await pwaManager.forceUpdateCache(); // Перезагружаем страницу setTimeout(() => { window.location.reload(); }, 1000); };