- Обновлены инструкции по тестированию PWA для мобильных и десктопных устройств. - Добавлены новые мета-теги и улучшены иконки для поддержки iOS и Windows. - Оптимизирован Service Worker для кэширования и обработки ошибок. - Реализована кнопка установки, отображающаяся только на мобильных устройствах, с различными инструкциями для разных браузеров. - Обновлен manifest.json с добавлением категорий и скриншотов.
416 lines
18 KiB
HTML
416 lines
18 KiB
HTML
<!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>
|