Улучшена поддержка PWA и мобильных устройств

- Обновлены инструкции по тестированию PWA для мобильных и десктопных устройств.
- Добавлены новые мета-теги и улучшены иконки для поддержки iOS и Windows.
- Оптимизирован Service Worker для кэширования и обработки ошибок.
- Реализована кнопка установки, отображающаяся только на мобильных устройствах, с различными инструкциями для разных браузеров.
- Обновлен manifest.json с добавлением категорий и скриншотов.
This commit is contained in:
Fovway 2025-10-20 09:35:32 +07:00
parent fbc2b5259c
commit 95401328c4
8 changed files with 691 additions and 32 deletions

View File

@ -22,25 +22,30 @@
## Как протестировать
### 1. Откройте тестовую страницу
### 1. Откройте тестовую страницу для мобильных устройств
```
http://localhost:3000/mobile-pwa-test.html
```
### 2. Откройте обычную тестовую страницу
```
http://localhost:3000/test-pwa.html
```
### 2. Проверьте требования PWA
Нажмите кнопку "Проверить статус PWA" - все пункты должны быть зелеными:
### 3. Проверьте требования PWA
На мобильной тестовой странице автоматически проверяются все требования:
- ✅ HTTPS или localhost
- ✅ Service Worker
- ✅ Manifest
- ✅ Иконки
- ❌ Уже установлено (должно быть красным, если не установлено)
### 3. Установка приложения
### 4. Установка приложения
- Если все проверки пройдены, появится кнопка "Установить приложение"
- Нажмите на неё для установки PWA
- Следуйте инструкциям браузера
- Следуйте инструкциям браузера или используйте инструкции на странице
### 4. Проверка в разных браузерах
### 5. Проверка в разных браузерах
#### Chrome/Edge:
- Откройте DevTools (F12)
@ -59,6 +64,39 @@ http://localhost:3000/test-pwa.html
- Выберите "На экран Домой"
- Приложение установится как PWA
#### ПК/Десктоп (Chrome, Edge, Firefox):
- Откройте сайт в браузере
- Нажмите на иконку установки в адресной строке (если доступна)
- Или используйте меню браузера → "Установить приложение"
- Или используйте меню браузера → "Создать ярлык"
## Новые улучшения для мобильных устройств
✅ **Улучшенный manifest.json:**
- Добавлены все необходимые размеры иконок (72x72, 96x96, 128x128, 144x144, 152x152, 192x192, 384x384, 512x512)
- Добавлены maskable иконки для Android
- Добавлены категории и скриншоты
- Улучшена совместимость с мобильными устройствами
✅ **Улучшенные мета-теги:**
- Добавлены все необходимые apple-touch-icon размеры
- Улучшена поддержка iOS Safari
- Добавлены мета-теги для Windows
- Настроен правильный статус-бар для iOS
✅ **Улучшенный Service Worker:**
- Кэширование всех иконок
- Улучшенная обработка ошибок
- Fallback для различных типов ресурсов
- Лучшая поддержка мобильных устройств
✅ **Улучшенный PWA Manager:**
- Определение мобильного Safari
- Разные инструкции для разных браузеров
- Улучшенная проверка установки PWA
- Поддержка различных режимов отображения
- **Кнопка установки показывается только на мобильных устройствах**
## Возможные проблемы и решения
### 1. Кнопка установки не появляется
@ -66,11 +104,14 @@ http://localhost:3000/test-pwa.html
- Приложение уже установлено
- Браузер не поддерживает PWA
- Не выполнены требования PWA
- **Вы используете ПК/десктоп (кнопка скрыта для ПК)**
**Решение:**
- Проверьте статус на тестовой странице
- Проверьте статус на мобильной тестовой странице
- Убедитесь, что используете HTTPS или localhost
- Проверьте консоль браузера на ошибки
- Для iOS Safari используйте инструкции "Добавить на главный экран"
- **На ПК используйте меню браузера для установки PWA**
### 2. Service Worker не регистрируется
**Причины:**
@ -125,6 +166,8 @@ navigator.serviceWorker.getRegistrations().then(console.log);
- ✅ `http://localhost:3000/sw.js` - должен возвращать JavaScript
- ✅ `http://localhost:3000/icons/icon-192x192.png` - должен возвращать PNG
- ✅ `http://localhost:3000/icons/icon-512x512.png` - должен возвращать PNG
- ✅ `http://localhost:3000/mobile-pwa-test.html` - мобильная тестовая страница
- ✅ `http://localhost:3000/test-pwa.html` - обычная тестовая страница
## Следующие шаги

View File

@ -10,15 +10,27 @@
<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-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="NoteJS" />
<meta name="apple-touch-fullscreen" content="yes" />
<meta name="msapplication-TileColor" content="#007bff" />
<meta name="msapplication-config" content="/browserconfig.xml" />
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
<meta name="application-name" content="NoteJS" />
<meta name="format-detection" content="telephone=no" />
<!-- 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="57x57" href="/icons/icon-48x48.png" />
<link rel="apple-touch-icon" sizes="60x60" href="/icons/icon-48x48.png" />
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png" />
<link rel="apple-touch-icon" sizes="76x76" href="/icons/icon-72x72.png" />
<link rel="apple-touch-icon" sizes="114x114" href="/icons/icon-128x128.png" />
<link rel="apple-touch-icon" sizes="120x120" href="/icons/icon-128x128.png" />
<link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
@ -109,25 +121,32 @@
e.preventDefault();
deferredPrompt = e;
// Показываем кнопку установки
const installButton = document.createElement('button');
installButton.textContent = 'Установить приложение';
installButton.className = 'btnSave';
installButton.style.marginTop = '10px';
installButton.style.width = '100%';
// Проверяем, является ли устройство мобильным
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 2) ||
window.matchMedia('(max-width: 768px)').matches;
installButton.addEventListener('click', () => {
deferredPrompt.prompt();
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('Пользователь установил приложение');
}
deferredPrompt = null;
installButton.remove();
// Показываем кнопку установки только на мобильных устройствах
if (isMobile) {
const installButton = document.createElement('button');
installButton.textContent = 'Установить приложение';
installButton.className = 'btnSave';
installButton.style.marginTop = '10px';
installButton.style.width = '100%';
installButton.addEventListener('click', () => {
deferredPrompt.prompt();
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('Пользователь установил приложение');
}
deferredPrompt = null;
installButton.remove();
});
});
});
document.querySelector('.auth-link').appendChild(installButton);
document.querySelector('.auth-link').appendChild(installButton);
}
});
// Обработка успешной установки

View File

@ -9,18 +9,75 @@
"orientation": "portrait-primary",
"scope": "/",
"lang": "ru",
"categories": ["productivity", "utilities"],
"screenshots": [
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"form_factor": "narrow"
}
],
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

415
public/mobile-pwa-test.html Normal file
View File

@ -0,0 +1,415 @@
<!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 на мобильных устройствах" />
<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="black-translucent" />
<meta name="apple-mobile-web-app-title" content="NoteJS" />
<meta name="apple-touch-fullscreen" content="yes" />
<meta name="msapplication-TileColor" content="#007bff" />
<meta name="msapplication-config" content="/browserconfig.xml" />
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
<meta name="application-name" content="NoteJS" />
<meta name="format-detection" content="telephone=no" />
<!-- 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="57x57" href="/icons/icon-48x48.png" />
<link rel="apple-touch-icon" sizes="60x60" href="/icons/icon-48x48.png" />
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png" />
<link rel="apple-touch-icon" sizes="76x76" href="/icons/icon-72x72.png" />
<link rel="apple-touch-icon" sizes="114x114" href="/icons/icon-128x128.png" />
<link rel="apple-touch-icon" sizes="120x120" href="/icons/icon-128x128.png" />
<link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
<!-- Manifest -->
<link rel="manifest" href="/manifest.json" />
<!-- Styles -->
<link rel="stylesheet" href="/style.css" />
<style>
.test-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.test-section {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.test-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #e9ecef;
}
.test-item:last-child {
border-bottom: none;
}
.status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.status.pass {
background: #d4edda;
color: #155724;
}
.status.fail {
background: #f8d7da;
color: #721c24;
}
.status.warning {
background: #fff3cd;
color: #856404;
}
.install-button {
background: #007bff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
width: 100%;
margin: 10px 0;
}
.install-button:hover {
background: #0056b3;
}
.install-button:disabled {
background: #6c757d;
cursor: not-allowed;
}
.instructions {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 6px;
padding: 15px;
margin: 15px 0;
}
.instructions h4 {
margin-top: 0;
color: #0066cc;
}
.instructions ol {
margin: 10px 0;
padding-left: 20px;
}
.instructions li {
margin: 5px 0;
}
.debug-info {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
max-height: 200px;
overflow-y: auto;
}
</style>
</head>
<body>
<div class="test-container">
<h1>📱 Тест PWA для мобильных устройств</h1>
<div class="test-section">
<h3>Проверка требований PWA</h3>
<div id="pwa-checks">
<div class="test-item">
<span>HTTPS или localhost</span>
<span class="status" id="https-check">Проверка...</span>
</div>
<div class="test-item">
<span>Service Worker</span>
<span class="status" id="sw-check">Проверка...</span>
</div>
<div class="test-item">
<span>Manifest</span>
<span class="status" id="manifest-check">Проверка...</span>
</div>
<div class="test-item">
<span>Иконки</span>
<span class="status" id="icons-check">Проверка...</span>
</div>
<div class="test-item">
<span>Уже установлено</span>
<span class="status" id="installed-check">Проверка...</span>
</div>
</div>
</div>
<div class="test-section">
<h3>Установка приложения</h3>
<div id="device-info" style="margin-bottom: 10px; padding: 10px; background: #e7f3ff; border-radius: 4px; font-size: 14px;"></div>
<button id="install-button" class="install-button" disabled>
Установить приложение
</button>
<div id="install-status"></div>
</div>
<div class="test-section">
<h3>Инструкции по установке</h3>
<div class="instructions">
<h4>Android Chrome/Edge:</h4>
<ol>
<li>Откройте сайт в Chrome или Edge</li>
<li>Нажмите кнопку "Установить приложение" выше</li>
<li>Или нажмите меню (⋮) → "Установить приложение"</li>
<li>Следуйте инструкциям браузера</li>
</ol>
</div>
<div class="instructions">
<h4>iOS Safari:</h4>
<ol>
<li>Откройте сайт в Safari</li>
<li>Нажмите кнопку "Поделиться" (□↗)</li>
<li>Выберите "На экран Домой"</li>
<li>Нажмите "Добавить"</li>
</ol>
</div>
<div class="instructions">
<h4>Другие браузеры:</h4>
<ol>
<li>Найдите опцию "Установить" в меню браузера</li>
<li>Или используйте "Добавить на главный экран"</li>
</ol>
</div>
</div>
<div class="test-section">
<h3>Отладочная информация</h3>
<button onclick="showDebugInfo()" class="install-button">Показать отладочную информацию</button>
<div id="debug-info" class="debug-info" style="display: none;"></div>
</div>
<div class="test-section">
<h3>Действия</h3>
<button onclick="clearCache()" class="install-button">Очистить кэш</button>
<button onclick="forceUpdate()" class="install-button">Принудительное обновление</button>
<button onclick="location.reload()" class="install-button">Перезагрузить страницу</button>
</div>
</div>
<script>
let deferredPrompt;
// Проверка требований PWA
function checkPWARequirements() {
// HTTPS или localhost
const isSecure = location.protocol === 'https:' || location.hostname === 'localhost';
document.getElementById('https-check').textContent = isSecure ? '✅ Да' : '❌ Нет';
document.getElementById('https-check').className = `status ${isSecure ? 'pass' : 'fail'}`;
// Service Worker
const hasSW = 'serviceWorker' in navigator;
document.getElementById('sw-check').textContent = hasSW ? '✅ Да' : '❌ Нет';
document.getElementById('sw-check').className = `status ${hasSW ? 'pass' : 'fail'}`;
// Manifest
const hasManifest = document.querySelector('link[rel="manifest"]') !== null;
document.getElementById('manifest-check').textContent = hasManifest ? '✅ Да' : '❌ Нет';
document.getElementById('manifest-check').className = `status ${hasManifest ? 'pass' : 'fail'}`;
// Иконки
const hasIcons = document.querySelector('link[rel="icon"]') !== null;
document.getElementById('icons-check').textContent = hasIcons ? '✅ Да' : '❌ Нет';
document.getElementById('icons-check').className = `status ${hasIcons ? 'pass' : 'fail'}`;
// Уже установлено
const isInstalled = window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone === true;
document.getElementById('installed-check').textContent = isInstalled ? '✅ Да' : '❌ Нет';
document.getElementById('installed-check').className = `status ${isInstalled ? 'warning' : 'pass'}`;
}
// Регистрация Service Worker
function registerServiceWorker() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('SW зарегистрирован успешно:', registration.scope);
})
.catch((error) => {
console.log('Ошибка регистрации SW:', error);
});
}
}
// Обработка установки
window.addEventListener('beforeinstallprompt', (e) => {
console.log('beforeinstallprompt событие получено');
e.preventDefault();
deferredPrompt = e;
// Проверяем, является ли устройство мобильным
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 2) ||
window.matchMedia('(max-width: 768px)').matches;
const installButton = document.getElementById('install-button');
if (isMobile) {
installButton.disabled = false;
installButton.textContent = '📱 Установить приложение';
} else {
installButton.disabled = true;
installButton.textContent = '💻 Установка доступна через меню браузера';
}
});
// Обработка успешной установки
window.addEventListener('appinstalled', () => {
console.log('PWA установлено успешно');
document.getElementById('install-status').innerHTML =
'<div style="color: green; margin-top: 10px;">✅ Приложение успешно установлено!</div>';
const installButton = document.getElementById('install-button');
installButton.disabled = true;
installButton.textContent = 'Приложение установлено';
});
// Установка приложения
document.getElementById('install-button').addEventListener('click', () => {
if (deferredPrompt) {
deferredPrompt.prompt();
deferredPrompt.userChoice.then((choiceResult) => {
console.log('Результат установки:', choiceResult.outcome);
if (choiceResult.outcome === 'accepted') {
document.getElementById('install-status').innerHTML =
'<div style="color: green; margin-top: 10px;">✅ Пользователь принял установку</div>';
} else {
document.getElementById('install-status').innerHTML =
'<div style="color: orange; margin-top: 10px;">⚠️ Пользователь отклонил установку</div>';
}
deferredPrompt = null;
});
} else {
document.getElementById('install-status').innerHTML =
'<div style="color: red; margin-top: 10px;">❌ Установка недоступна. Попробуйте использовать меню браузера.</div>';
}
});
// Показать отладочную информацию
function showDebugInfo() {
const debugInfo = document.getElementById('debug-info');
const info = {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
onLine: navigator.onLine,
cookieEnabled: navigator.cookieEnabled,
displayMode: window.matchMedia('(display-mode: standalone)').matches ? 'standalone' : 'browser',
standalone: window.navigator.standalone,
hasServiceWorker: 'serviceWorker' in navigator,
hasDeferredPrompt: deferredPrompt !== null,
location: {
href: location.href,
protocol: location.protocol,
hostname: location.hostname,
port: location.port
}
};
debugInfo.textContent = JSON.stringify(info, null, 2);
debugInfo.style.display = debugInfo.style.display === 'none' ? 'block' : 'none';
}
// Очистка кэша
async function clearCache() {
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
try {
navigator.serviceWorker.controller.postMessage({
type: 'CLEAR_ALL_CACHE'
});
alert('Запрос на очистку кэша отправлен');
} catch (error) {
alert('Ошибка при очистке кэша: ' + error.message);
}
} else {
alert('Service Worker не доступен');
}
}
// Принудительное обновление
async function forceUpdate() {
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.getRegistration();
if (registration) {
await registration.update();
alert('Проверка обновлений завершена');
}
} catch (error) {
alert('Ошибка при проверке обновлений: ' + error.message);
}
} else {
alert('Service Worker не поддерживается');
}
}
// Проверка типа устройства
function checkDeviceType() {
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 2) ||
window.matchMedia('(max-width: 768px)').matches;
const deviceInfo = document.getElementById('device-info');
if (isMobile) {
deviceInfo.innerHTML = '📱 <strong>Мобильное устройство</strong> - кнопка установки доступна';
deviceInfo.style.background = '#d4edda';
deviceInfo.style.color = '#155724';
} else {
deviceInfo.innerHTML = '💻 <strong>ПК/Десктоп</strong> - кнопка установки скрыта (PWA доступно через меню браузера)';
deviceInfo.style.background = '#fff3cd';
deviceInfo.style.color = '#856404';
}
}
// Инициализация
document.addEventListener('DOMContentLoaded', () => {
checkDeviceType();
checkPWARequirements();
registerServiceWorker();
});
</script>
</body>
</html>

View File

@ -10,14 +10,27 @@
<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-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="NoteJS" />
<meta name="apple-touch-fullscreen" content="yes" />
<meta name="msapplication-TileColor" content="#007bff" />
<meta name="msapplication-config" content="/browserconfig.xml" />
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
<meta name="application-name" content="NoteJS" />
<meta name="format-detection" content="telephone=no" />
<!-- 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="57x57" href="/icons/icon-48x48.png" />
<link rel="apple-touch-icon" sizes="60x60" href="/icons/icon-48x48.png" />
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png" />
<link rel="apple-touch-icon" sizes="76x76" href="/icons/icon-72x72.png" />
<link rel="apple-touch-icon" sizes="114x114" href="/icons/icon-128x128.png" />
<link rel="apple-touch-icon" sizes="120x120" href="/icons/icon-128x128.png" />
<link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
<link rel="mask-icon" href="/icon.svg" color="#007bff" />

View File

@ -80,14 +80,47 @@ class PWAManager {
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');
installButton.textContent = '📱 Установить приложение';
// Разный текст для разных браузеров
if (this.isMobileSafari()) {
installButton.textContent = '📱 Добавить на главный экран';
} else {
installButton.textContent = '📱 Установить приложение';
}
installButton.className = 'btnSave';
installButton.style.marginTop = '10px';
installButton.style.width = '100%';
@ -132,6 +165,13 @@ class PWAManager {
// Установка приложения
installApp() {
console.log('Попытка установки приложения');
if (this.isMobileSafari()) {
// Для iOS Safari показываем инструкции
this.showSafariInstructions();
return;
}
if (this.deferredPrompt) {
this.deferredPrompt.prompt();
this.deferredPrompt.userChoice.then((choiceResult) => {
@ -144,9 +184,32 @@ class PWAManager {
});
} 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');
@ -167,7 +230,9 @@ class PWAManager {
// Проверка статуса PWA
isPWAInstalled() {
return window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone === true;
window.navigator.standalone === true ||
document.referrer.includes('android-app://') ||
window.matchMedia('(display-mode: fullscreen)').matches;
}
// Получение информации о PWA
@ -177,7 +242,12 @@ class PWAManager {
isOnline: navigator.onLine,
hasServiceWorker: 'serviceWorker' in navigator,
userAgent: navigator.userAgent,
hasDeferredPrompt: this.deferredPrompt !== null
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'
};
}

View File

@ -10,14 +10,27 @@
<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-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="NoteJS" />
<meta name="apple-touch-fullscreen" content="yes" />
<meta name="msapplication-TileColor" content="#007bff" />
<meta name="msapplication-config" content="/browserconfig.xml" />
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
<meta name="application-name" content="NoteJS" />
<meta name="format-detection" content="telephone=no" />
<!-- 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="57x57" href="/icons/icon-48x48.png" />
<link rel="apple-touch-icon" sizes="60x60" href="/icons/icon-48x48.png" />
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png" />
<link rel="apple-touch-icon" sizes="76x76" href="/icons/icon-72x72.png" />
<link rel="apple-touch-icon" sizes="114x114" href="/icons/icon-128x128.png" />
<link rel="apple-touch-icon" sizes="120x120" href="/icons/icon-128x128.png" />
<link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
<link rel="mask-icon" href="/icon.svg" color="#007bff" />

View File

@ -1,5 +1,5 @@
// Service Worker для NoteJS
const APP_VERSION = '1.0.2';
const APP_VERSION = '1.0.3';
const CACHE_NAME = `notejs-v${APP_VERSION}`;
const STATIC_CACHE_NAME = `notejs-static-v${APP_VERSION}`;
@ -9,8 +9,16 @@ const STATIC_FILES = [
'/index.html',
'/style.css',
'/manifest.json',
'/icons/icon-72x72.png',
'/icons/icon-96x96.png',
'/icons/icon-128x128.png',
'/icons/icon-144x144.png',
'/icons/icon-152x152.png',
'/icons/icon-192x192.png',
'/icons/icon-512x512.png'
'/icons/icon-384x384.png',
'/icons/icon-512x512.png',
'/icon.svg',
'/logo.svg'
];
// Установка Service Worker
@ -71,6 +79,11 @@ self.addEventListener('fetch', (event) => {
return;
}
// Пропускаем запросы к внешним ресурсам
if (url.origin !== location.origin) {
return;
}
// Обрабатываем только GET запросы
if (request.method === 'GET') {
event.respondWith(
@ -112,6 +125,22 @@ async function handleRequest(request) {
}
}
// Fallback для иконок
if (request.url.includes('/icons/')) {
const fallbackIcon = await caches.match('/icons/icon-192x192.png');
if (fallbackIcon) {
return fallbackIcon;
}
}
// Fallback для CSS
if (request.url.includes('/style.css')) {
const fallbackCSS = await caches.match('/style.css');
if (fallbackCSS) {
return fallbackCSS;
}
}
throw error;
}
}