Обновлены зависимости и улучшена поддержка PWA

- Добавлена библиотека pngjs для работы с PNG изображениями
- Добавлены мета-теги для улучшения поддержки PWA на страницах: index.html, notes.html, profile.html, register.html
- Обновлен сервисный работник для улучшенного кэширования и обработки запросов
- Добавлены функции для отладки PWA в консоли
This commit is contained in:
Fovway 2025-10-20 09:09:45 +07:00
parent 4600dc61b7
commit 9ecc787719
10 changed files with 438 additions and 179 deletions

136
PWA-TESTING.md Normal file
View File

@ -0,0 +1,136 @@
# 🚀 Тестирование PWA для NoteJS
## Что было сделано
✅ **Созданы файлы PWA:**
- `manifest.json` - манифест приложения
- `sw.js` - сервис-воркер для кэширования
- `pwa.js` - JavaScript класс для управления PWA
- `icon.svg` - SVG иконка приложения
- `logo.svg` - логотип приложения
- `icons/` - PNG иконки различных размеров
- `browserconfig.xml` - конфигурация для Windows
✅ **Обновлены HTML страницы:**
- Добавлены PWA мета-теги
- Подключены иконки и манифест
- Добавлен скрипт регистрации Service Worker
✅ **Настроен сервер:**
- Правильные заголовки для PWA файлов
- Поддержка кэширования
## Как протестировать
### 1. Откройте тестовую страницу
```
http://localhost:3000/test-pwa.html
```
### 2. Проверьте требования PWA
Нажмите кнопку "Проверить статус PWA" - все пункты должны быть зелеными:
- ✅ HTTPS или localhost
- ✅ Service Worker
- ✅ Manifest
- ✅ Иконки
- ❌ Уже установлено (должно быть красным, если не установлено)
### 3. Установка приложения
- Если все проверки пройдены, появится кнопка "Установить приложение"
- Нажмите на неё для установки PWA
- Следуйте инструкциям браузера
### 4. Проверка в разных браузерах
#### Chrome/Edge:
- Откройте DevTools (F12)
- Перейдите в Application → Manifest
- Проверьте, что манифест загружается без ошибок
- В Application → Service Workers проверьте статус SW
#### Firefox:
- Откройте DevTools (F12)
- Перейдите в Application → Manifest
- Проверьте манифест
#### Safari (iOS):
- Откройте сайт в Safari
- Нажмите кнопку "Поделиться"
- Выберите "На экран Домой"
- Приложение установится как PWA
## Возможные проблемы и решения
### 1. Кнопка установки не появляется
**Причины:**
- Приложение уже установлено
- Браузер не поддерживает PWA
- Не выполнены требования PWA
**Решение:**
- Проверьте статус на тестовой странице
- Убедитесь, что используете HTTPS или localhost
- Проверьте консоль браузера на ошибки
### 2. Service Worker не регистрируется
**Причины:**
- Ошибки в коде SW
- Проблемы с кэшированием файлов
**Решение:**
- Откройте DevTools → Application → Service Workers
- Проверьте ошибки в консоли
- Попробуйте очистить кэш
### 3. Ошибка "Download error or resource isn't a valid image"
**Причины:**
- PNG иконки повреждены или имеют неправильный размер
- Иконки не являются валидными PNG файлами
**Решение:**
- ✅ **ИСПРАВЛЕНО**: Созданы правильные PNG иконки с помощью pngjs
- Проверьте, что иконки имеют правильные размеры (192x192, 512x512)
- Убедитесь, что файлы иконок валидные PNG
### 4. Предупреждение о deprecated meta tag
**Причины:**
- Использование устаревшего `apple-mobile-web-app-capable`
**Решение:**
- ✅ **ИСПРАВЛЕНО**: Добавлен современный `mobile-web-app-capable`
- Оба тега теперь присутствуют для совместимости
## Отладка
### Консоль браузера
Откройте DevTools (F12) и проверьте консоль на ошибки:
```javascript
// Проверить статус PWA
window.debugPWA();
// Проверить Service Worker
navigator.serviceWorker.getRegistrations().then(console.log);
```
### Lighthouse
Запустите аудит Lighthouse в Chrome DevTools:
1. Откройте DevTools (F12)
2. Перейдите в Lighthouse
3. Выберите "Progressive Web App"
4. Нажмите "Generate report"
## Файлы для проверки
- ✅ `http://localhost:3000/manifest.json` - должен возвращать JSON
- ✅ `http://localhost:3000/sw.js` - должен возвращать JavaScript
- ✅ `http://localhost:3000/icons/icon-192x192.png` - должен возвращать PNG
- ✅ `http://localhost:3000/icons/icon-512x512.png` - должен возвращать PNG
## Следующие шаги
После успешного тестирования:
1. Удалите тестовую страницу `test-pwa.html`
2. Настройте HTTPS для продакшена
3. Добавьте реальные скриншоты в манифест
4. Создайте качественные иконки с помощью дизайнера
5. Настройте push-уведомления (опционально)

9
package-lock.json generated
View File

@ -28,6 +28,7 @@
"marked": "^16.4.0",
"multer": "^2.0.0-rc.4",
"node-fetch": "^3.3.2",
"pngjs": "^7.0.0",
"sqlite3": "^5.1.7"
},
"devDependencies": {
@ -2603,6 +2604,14 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pngjs": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
"engines": {
"node": ">=14.19.0"
}
},
"node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",

View File

@ -34,6 +34,7 @@
"marked": "^16.4.0",
"multer": "^2.0.0-rc.4",
"node-fetch": "^3.3.2",
"pngjs": "^7.0.0",
"sqlite3": "^5.1.7"
},
"devDependencies": {

View File

@ -8,6 +8,7 @@
<!-- PWA Meta Tags -->
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
<meta name="theme-color" content="#007bff" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="NoteJS" />

View File

@ -8,6 +8,7 @@
<!-- PWA Meta Tags -->
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
<meta name="theme-color" content="#007bff" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="NoteJS" />

View File

@ -8,6 +8,7 @@
<!-- PWA Meta Tags -->
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
<meta name="theme-color" content="#007bff" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="NoteJS" />

View File

@ -6,9 +6,20 @@ class PWAManager {
}
init() {
console.log('PWA Manager инициализирован');
this.registerServiceWorker();
this.setupInstallPrompt();
this.setupAppInstalled();
this.checkPWARequirements();
}
// Проверка требований 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
@ -33,6 +44,8 @@ class PWAManager {
console.log('Ошибка регистрации SW:', error);
});
});
} else {
console.log('Service Worker не поддерживается');
}
}
@ -49,6 +62,7 @@ class PWAManager {
// Настройка промпта установки
setupInstallPrompt() {
window.addEventListener('beforeinstallprompt', (e) => {
console.log('beforeinstallprompt событие получено');
e.preventDefault();
this.deferredPrompt = e;
this.showInstallButton();
@ -57,10 +71,12 @@ class PWAManager {
// Показ кнопки установки
showInstallButton() {
console.log('Попытка показать кнопку установки');
// Проверяем, не установлено ли уже приложение
if (window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone === true) {
return; // Приложение уже установлено
if (this.isPWAInstalled()) {
console.log('Приложение уже установлено');
return;
}
const installButton = this.createInstallButton();
@ -75,10 +91,7 @@ class PWAManager {
installButton.style.marginTop = '10px';
installButton.style.width = '100%';
installButton.style.fontSize = '14px';
installButton.style.display = 'flex';
installButton.style.alignItems = 'center';
installButton.style.justifyContent = 'center';
installButton.style.gap = '8px';
installButton.id = 'pwa-install-button';
installButton.addEventListener('click', () => {
this.installApp();
@ -89,6 +102,12 @@ class PWAManager {
// Добавление кнопки на страницу
addInstallButtonToPage(installButton) {
// Удаляем существующую кнопку, если есть
const existingButton = document.getElementById('pwa-install-button');
if (existingButton) {
existingButton.remove();
}
// Ищем подходящее место для кнопки
const authLink = document.querySelector('.auth-link');
const footer = document.querySelector('.footer');
@ -96,42 +115,46 @@ class PWAManager {
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.deferredPrompt) {
this.deferredPrompt.prompt();
this.deferredPrompt.userChoice.then((choiceResult) => {
console.log('Результат установки:', choiceResult.outcome);
if (choiceResult.outcome === 'accepted') {
console.log('Пользователь установил приложение');
this.trackInstallation();
}
this.deferredPrompt = null;
this.removeInstallButton();
});
} else {
console.log('deferredPrompt не доступен');
}
}
// Удаление кнопки установки
removeInstallButton() {
const installButton = document.querySelector('button[style*="Установить приложение"]');
const installButton = document.getElementById('pwa-install-button');
if (installButton) {
installButton.remove();
console.log('Кнопка установки удалена');
}
}
// Отслеживание установки
trackInstallation() {
// Здесь можно добавить аналитику
console.log('PWA установлено успешно');
}
// Обработка успешной установки
setupAppInstalled() {
window.addEventListener('appinstalled', () => {
@ -152,7 +175,8 @@ class PWAManager {
isInstalled: this.isPWAInstalled(),
isOnline: navigator.onLine,
hasServiceWorker: 'serviceWorker' in navigator,
userAgent: navigator.userAgent
userAgent: navigator.userAgent,
hasDeferredPrompt: this.deferredPrompt !== null
};
}
}
@ -162,3 +186,8 @@ const pwaManager = new PWAManager();
// Экспорт для использования в других скриптах
window.PWAManager = pwaManager;
// Добавляем глобальную функцию для отладки
window.debugPWA = () => {
console.log('PWA Debug Info:', pwaManager.getPWAInfo());
};

View File

@ -8,6 +8,7 @@
<!-- PWA Meta Tags -->
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
<meta name="theme-color" content="#007bff" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="NoteJS" />

View File

@ -1,34 +1,15 @@
// Service Worker для NoteJS
const CACHE_NAME = 'notejs-v1.0.0';
const STATIC_CACHE_NAME = 'notejs-static-v1.0.0';
const DYNAMIC_CACHE_NAME = 'notejs-dynamic-v1.0.0';
const CACHE_NAME = 'notejs-v1.0.1';
const STATIC_CACHE_NAME = 'notejs-static-v1.0.1';
// Файлы для кэширования при установке
const STATIC_FILES = [
'/',
'/index.html',
'/login.html',
'/register.html',
'/notes.html',
'/profile.html',
'/style.css',
'/app.js',
'/login.js',
'/register.js',
'/profile.js',
'/icon.svg',
'/logo.svg',
'/manifest.json',
'/icons/icon-192x192.png',
'/icons/icon-512x512.png',
'https://cdnjs.cloudflare.com/ajax/libs/iconify/2.0.0/iconify.min.js'
];
// Файлы, которые не нужно кэшировать
const EXCLUDE_FROM_CACHE = [
'/api/',
'/uploads/',
'/database/'
'/icons/icon-512x512.png'
];
// Установка Service Worker
@ -47,6 +28,8 @@ self.addEventListener('install', (event) => {
})
.catch((error) => {
console.error('[SW] Ошибка при кэшировании статических файлов:', error);
// Продолжаем работу даже если кэширование не удалось
return self.skipWaiting();
})
);
});
@ -61,7 +44,7 @@ self.addEventListener('activate', (event) => {
return Promise.all(
cacheNames.map((cacheName) => {
// Удаляем старые кэши
if (cacheName !== STATIC_CACHE_NAME && cacheName !== DYNAMIC_CACHE_NAME) {
if (cacheName !== STATIC_CACHE_NAME) {
console.log('[SW] Удаление старого кэша:', cacheName);
return caches.delete(cacheName);
}
@ -81,11 +64,13 @@ self.addEventListener('fetch', (event) => {
const url = new URL(request.url);
// Пропускаем запросы к API и загрузкам
if (EXCLUDE_FROM_CACHE.some(pattern => url.pathname.startsWith(pattern))) {
if (url.pathname.startsWith('/api/') ||
url.pathname.startsWith('/uploads/') ||
url.pathname.startsWith('/database/')) {
return;
}
// Стратегия кэширования: Cache First для статических файлов, Network First для HTML
// Обрабатываем только GET запросы
if (request.method === 'GET') {
event.respondWith(
handleRequest(request)
@ -94,142 +79,42 @@ self.addEventListener('fetch', (event) => {
});
async function handleRequest(request) {
const url = new URL(request.url);
try {
// Для HTML файлов используем Network First стратегию
if (request.headers.get('accept')?.includes('text/html')) {
return await networkFirstStrategy(request);
// Сначала пытаемся получить из кэша
const cachedResponse = await caches.match(request);
if (cachedResponse) {
console.log('[SW] Запрос из кэша:', request.url);
return cachedResponse;
}
// Для статических ресурсов используем Cache First стратегию
return await cacheFirstStrategy(request);
} catch (error) {
console.error('[SW] Ошибка при обработке запроса:', error);
// Fallback для HTML страниц
if (request.headers.get('accept')?.includes('text/html')) {
return await caches.match('/index.html');
}
throw error;
}
}
// Стратегия Cache First (для статических ресурсов)
async function cacheFirstStrategy(request) {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
console.log('[SW] Запрос из кэша:', request.url);
return cachedResponse;
}
console.log('[SW] Запрос к сети:', request.url);
const networkResponse = await fetch(request);
// Кэшируем успешные ответы
if (networkResponse.ok) {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
cache.put(request, networkResponse.clone());
}
return networkResponse;
}
// Стратегия Network First (для HTML страниц)
async function networkFirstStrategy(request) {
try {
console.log('[SW] Запрос к сети (Network First):', request.url);
// Если нет в кэше, загружаем из сети
console.log('[SW] Запрос к сети:', request.url);
const networkResponse = await fetch(request);
// Кэшируем успешные ответы
if (networkResponse.ok) {
const cache = await caches.open(DYNAMIC_CACHE_NAME);
const cache = await caches.open(STATIC_CACHE_NAME);
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
console.log('[SW] Сеть недоступна, поиск в кэше:', request.url);
const cachedResponse = await caches.match(request);
console.error('[SW] Ошибка при обработке запроса:', error);
if (cachedResponse) {
return cachedResponse;
}
// Fallback на главную страницу
// Fallback для HTML страниц
if (request.headers.get('accept')?.includes('text/html')) {
return await caches.match('/index.html');
const fallbackResponse = await caches.match('/index.html');
if (fallbackResponse) {
return fallbackResponse;
}
}
throw error;
}
}
// Обработка push уведомлений (для будущего использования)
self.addEventListener('push', (event) => {
console.log('[SW] Получено push уведомление');
const options = {
body: event.data ? event.data.text() : 'Новое уведомление от NoteJS',
icon: '/icons/icon-192x192.png',
badge: '/icons/icon-96x96.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
},
actions: [
{
action: 'explore',
title: 'Открыть приложение',
icon: '/icons/icon-96x96.png'
},
{
action: 'close',
title: 'Закрыть',
icon: '/icons/icon-96x96.png'
}
]
};
event.waitUntil(
self.registration.showNotification('NoteJS', options)
);
});
// Обработка кликов по уведомлениям
self.addEventListener('notificationclick', (event) => {
console.log('[SW] Клик по уведомлению:', event.action);
event.notification.close();
if (event.action === 'explore') {
event.waitUntil(
clients.openWindow('/')
);
}
});
// Синхронизация в фоне (для будущего использования)
self.addEventListener('sync', (event) => {
console.log('[SW] Фоновая синхронизация:', event.tag);
if (event.tag === 'background-sync') {
event.waitUntil(
doBackgroundSync()
);
}
});
async function doBackgroundSync() {
// Здесь можно добавить логику синхронизации данных
console.log('[SW] Выполнение фоновой синхронизации');
}
// Обработка сообщений от основного потока
self.addEventListener('message', (event) => {
console.log('[SW] Получено сообщение:', event.data);
@ -241,26 +126,4 @@ self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'GET_VERSION') {
event.ports[0].postMessage({ version: CACHE_NAME });
}
});
// Периодическая очистка кэша
self.addEventListener('periodicsync', (event) => {
if (event.tag === 'cache-cleanup') {
event.waitUntil(cleanupCache());
}
});
async function cleanupCache() {
const cacheNames = await caches.keys();
const oldCaches = cacheNames.filter(name =>
name !== STATIC_CACHE_NAME &&
name !== DYNAMIC_CACHE_NAME &&
name.startsWith('notejs-')
);
await Promise.all(
oldCaches.map(name => caches.delete(name))
);
console.log('[SW] Очистка старых кэшей завершена');
}
});

217
public/test-pwa.html Normal file
View File

@ -0,0 +1,217 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Тест PWA - NoteJS</title>
<!-- PWA Meta Tags -->
<meta name="description" content="Тест PWA для NoteJS" />
<meta name="theme-color" content="#007bff" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="NoteJS" />
<!-- Icons -->
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/icons/icon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
<!-- Manifest -->
<link rel="manifest" href="/manifest.json" />
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.status {
padding: 10px;
margin: 10px 0;
border-radius: 5px;
}
.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
.info { background: #d1ecf1; color: #0c5460; }
button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 5px;
}
button:hover { background: #0056b3; }
button:disabled { background: #6c757d; cursor: not-allowed; }
</style>
</head>
<body>
<div class="container">
<h1>🔧 Тест PWA для NoteJS</h1>
<div id="status-container">
<div class="status info">Проверяем требования PWA...</div>
</div>
<div>
<button onclick="checkPWAStatus()">Проверить статус PWA</button>
<button onclick="installPWA()" id="install-btn" disabled>Установить приложение</button>
<button onclick="clearCache()">Очистить кэш</button>
</div>
<div id="debug-info" style="margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 5px; font-family: monospace; font-size: 12px;">
<h3>Отладочная информация:</h3>
<div id="debug-content"></div>
</div>
</div>
<!-- PWA Script -->
<script src="/pwa.js"></script>
<script>
let deferredPrompt;
// Проверка статуса PWA
function checkPWAStatus() {
const statusContainer = document.getElementById('status-container');
const debugContent = document.getElementById('debug-content');
// Очищаем предыдущие статусы
statusContainer.innerHTML = '';
debugContent.innerHTML = '';
// Проверяем требования PWA
const checks = [
{
name: 'HTTPS или localhost',
status: location.protocol === 'https:' || location.hostname === 'localhost',
description: `Протокол: ${location.protocol}, Хост: ${location.hostname}`
},
{
name: 'Service Worker',
status: 'serviceWorker' in navigator,
description: 'Поддержка Service Worker API'
},
{
name: 'Manifest',
status: document.querySelector('link[rel="manifest"]') !== null,
description: 'Манифест PWA подключен'
},
{
name: 'Иконки',
status: document.querySelector('link[rel="icon"]') !== null,
description: 'Иконки приложения подключены'
},
{
name: 'Уже установлено',
status: window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true,
description: 'Приложение уже установлено как PWA'
}
];
let allPassed = true;
checks.forEach(check => {
const statusDiv = document.createElement('div');
statusDiv.className = `status ${check.status ? 'success' : 'error'}`;
statusDiv.innerHTML = `${check.status ? '✅' : '❌'} ${check.name}: ${check.description}`;
statusContainer.appendChild(statusDiv);
if (!check.status) allPassed = false;
});
// Общая оценка
const overallDiv = document.createElement('div');
overallDiv.className = `status ${allPassed ? 'success' : 'error'}`;
overallDiv.innerHTML = `${allPassed ? '✅' : '❌'} Общий статус: ${allPassed ? 'PWA готово к установке' : 'Есть проблемы с PWA'}`;
statusContainer.appendChild(overallDiv);
// Отладочная информация
const debugInfo = {
userAgent: navigator.userAgent,
isOnline: navigator.onLine,
hasServiceWorker: 'serviceWorker' in navigator,
isStandalone: window.matchMedia('(display-mode: standalone)').matches,
isIOSStandalone: window.navigator.standalone === true,
hasDeferredPrompt: deferredPrompt !== null,
pwaInfo: window.PWAManager ? window.PWAManager.getPWAInfo() : 'PWA Manager не доступен'
};
debugContent.innerHTML = JSON.stringify(debugInfo, null, 2);
// Активируем кнопку установки если доступно
const installBtn = document.getElementById('install-btn');
if (deferredPrompt && !debugInfo.isStandalone && !debugInfo.isIOSStandalone) {
installBtn.disabled = false;
}
}
// Установка PWA
function installPWA() {
if (deferredPrompt) {
deferredPrompt.prompt();
deferredPrompt.userChoice.then((choiceResult) => {
console.log('Результат установки:', choiceResult.outcome);
if (choiceResult.outcome === 'accepted') {
alert('Приложение установлено!');
}
deferredPrompt = null;
document.getElementById('install-btn').disabled = true;
});
} else {
alert('Установка недоступна. Возможно, приложение уже установлено или браузер не поддерживает установку PWA.');
}
}
// Очистка кэша
function clearCache() {
if ('caches' in window) {
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
console.log('Удаление кэша:', cacheName);
return caches.delete(cacheName);
})
);
}).then(() => {
alert('Кэш очищен! Перезагрузите страницу.');
});
} else {
alert('Кэш не поддерживается в этом браузере.');
}
}
// Обработка события beforeinstallprompt
window.addEventListener('beforeinstallprompt', (e) => {
console.log('beforeinstallprompt событие получено');
e.preventDefault();
deferredPrompt = e;
document.getElementById('install-btn').disabled = false;
});
// Обработка успешной установки
window.addEventListener('appinstalled', () => {
console.log('PWA установлено успешно');
alert('Приложение установлено успешно!');
document.getElementById('install-btn').disabled = true;
});
// Автоматическая проверка при загрузке
window.addEventListener('load', () => {
setTimeout(checkPWAStatus, 1000);
});
</script>
</body>
</html>