✨ Добавлена поддержка PWA для улучшения пользовательского опыта
- Реализованы маршруты для обслуживания PWA файлов: manifest.json, sw.js и browserconfig.xml - Добавлены мета-теги и иконки для PWA в HTML страницах - Внедрена регистрация сервисного работника для кэширования и оффлайн-доступа - Обновлены страницы входа, регистрации, профиля и заметок для поддержки PWA
This commit is contained in:
parent
431d51c483
commit
4600dc61b7
9
public/browserconfig.xml
Normal file
9
public/browserconfig.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/icons/icon-152x152.png"/>
|
||||
<TileColor>#007bff</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
41
public/icon.svg
Normal file
41
public/icon.svg
Normal file
@ -0,0 +1,41 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Фон -->
|
||||
<rect width="512" height="512" rx="80" fill="#007bff"/>
|
||||
|
||||
<!-- Основная иконка заметки -->
|
||||
<rect x="80" y="100" width="280" height="360" rx="20" fill="white" stroke="#e3f2fd" stroke-width="4"/>
|
||||
|
||||
<!-- Заголовок заметки -->
|
||||
<rect x="100" y="120" width="240" height="20" rx="10" fill="#e3f2fd"/>
|
||||
|
||||
<!-- Строки текста -->
|
||||
<rect x="100" y="160" width="200" height="12" rx="6" fill="#e3f2fd"/>
|
||||
<rect x="100" y="180" width="180" height="12" rx="6" fill="#e3f2fd"/>
|
||||
<rect x="100" y="200" width="220" height="12" rx="6" fill="#e3f2fd"/>
|
||||
|
||||
<!-- Список -->
|
||||
<circle cx="110" cy="240" r="4" fill="#007bff"/>
|
||||
<rect x="125" y="236" width="120" height="8" rx="4" fill="#e3f2fd"/>
|
||||
|
||||
<circle cx="110" cy="260" r="4" fill="#007bff"/>
|
||||
<rect x="125" y="256" width="100" height="8" rx="4" fill="#e3f2fd"/>
|
||||
|
||||
<circle cx="110" cy="280" r="4" fill="#007bff"/>
|
||||
<rect x="125" y="276" width="140" height="8" rx="4" fill="#e3f2fd"/>
|
||||
|
||||
<!-- Код блок -->
|
||||
<rect x="100" y="320" width="240" height="60" rx="8" fill="#f5f5f5" stroke="#e0e0e0" stroke-width="2"/>
|
||||
<rect x="110" y="330" width="40" height="6" rx="3" fill="#007bff"/>
|
||||
<rect x="110" y="340" width="60" height="6" rx="3" fill="#666"/>
|
||||
<rect x="110" y="350" width="50" height="6" rx="3" fill="#666"/>
|
||||
<rect x="110" y="360" width="30" height="6" rx="3" fill="#007bff"/>
|
||||
|
||||
<!-- Тег -->
|
||||
<rect x="100" y="400" width="60" height="20" rx="10" fill="#e7f3ff" stroke="#007bff" stroke-width="2"/>
|
||||
<text x="130" y="413" text-anchor="middle" font-family="Arial, sans-serif" font-size="10" font-weight="bold" fill="#007bff">#tag</text>
|
||||
|
||||
<!-- Дополнительные элементы для живости -->
|
||||
<circle cx="400" cy="150" r="8" fill="white" opacity="0.3"/>
|
||||
<circle cx="450" cy="200" r="6" fill="white" opacity="0.2"/>
|
||||
<circle cx="420" cy="300" r="10" fill="white" opacity="0.25"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@ -4,7 +4,30 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Вход в систему заметок</title>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<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" />
|
||||
<meta name="msapplication-TileColor" content="#007bff" />
|
||||
<meta name="msapplication-config" content="/browserconfig.xml" />
|
||||
|
||||
<!-- 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" />
|
||||
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
|
||||
|
||||
<!-- Manifest -->
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iconify/2.0.0/iconify.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@ -50,5 +73,66 @@
|
||||
<p>Создатель: <span>Fovway</span></p>
|
||||
</div>
|
||||
<script src="/login.js"></script>
|
||||
|
||||
<!-- PWA Service Worker Registration -->
|
||||
<script>
|
||||
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) {
|
||||
// Новый контент доступен, можно показать уведомление
|
||||
if (confirm('Доступна новая версия приложения. Обновить?')) {
|
||||
newWorker.postMessage({ type: 'SKIP_WAITING' });
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Ошибка регистрации SW:', error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Обработка установки PWA
|
||||
let deferredPrompt;
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
e.preventDefault();
|
||||
deferredPrompt = e;
|
||||
|
||||
// Показываем кнопку установки
|
||||
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);
|
||||
});
|
||||
|
||||
// Обработка успешной установки
|
||||
window.addEventListener('appinstalled', () => {
|
||||
console.log('PWA установлено успешно');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
47
public/logo.svg
Normal file
47
public/logo.svg
Normal file
@ -0,0 +1,47 @@
|
||||
<svg width="400" height="120" viewBox="0 0 400 120" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Фон -->
|
||||
<rect width="400" height="120" fill="white"/>
|
||||
|
||||
<!-- Иконка логотипа -->
|
||||
<g transform="translate(20, 20)">
|
||||
<!-- Основная иконка заметки -->
|
||||
<rect x="0" y="0" width="60" height="80" rx="8" fill="#007bff" stroke="#0056b3" stroke-width="2"/>
|
||||
|
||||
<!-- Заголовок заметки -->
|
||||
<rect x="8" y="8" width="44" height="6" rx="3" fill="white"/>
|
||||
|
||||
<!-- Строки текста -->
|
||||
<rect x="8" y="20" width="36" height="4" rx="2" fill="white" opacity="0.8"/>
|
||||
<rect x="8" y="28" width="32" height="4" rx="2" fill="white" opacity="0.8"/>
|
||||
<rect x="8" y="36" width="40" height="4" rx="2" fill="white" opacity="0.8"/>
|
||||
|
||||
<!-- Список -->
|
||||
<circle cx="12" cy="50" r="2" fill="white"/>
|
||||
<rect x="18" y="49" width="20" height="2" rx="1" fill="white" opacity="0.8"/>
|
||||
|
||||
<circle cx="12" cy="58" r="2" fill="white"/>
|
||||
<rect x="18" y="57" width="16" height="2" rx="1" fill="white" opacity="0.8"/>
|
||||
|
||||
<!-- Тег -->
|
||||
<rect x="8" y="68" width="20" height="8" rx="4" fill="white" opacity="0.9"/>
|
||||
<text x="18" y="73" text-anchor="middle" font-family="Arial, sans-serif" font-size="4" font-weight="bold" fill="#007bff">#</text>
|
||||
</g>
|
||||
|
||||
<!-- Текст логотипа -->
|
||||
<g transform="translate(100, 0)">
|
||||
<!-- Название приложения -->
|
||||
<text x="0" y="45" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="#007bff">
|
||||
NoteJS
|
||||
</text>
|
||||
|
||||
<!-- Подзаголовок -->
|
||||
<text x="0" y="65" font-family="Arial, sans-serif" font-size="14" fill="#666">
|
||||
Система заметок
|
||||
</text>
|
||||
|
||||
<!-- Дополнительные элементы -->
|
||||
<circle cx="280" cy="25" r="3" fill="#007bff" opacity="0.3"/>
|
||||
<circle cx="320" cy="35" r="2" fill="#007bff" opacity="0.2"/>
|
||||
<circle cx="300" cy="55" r="4" fill="#007bff" opacity="0.25"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@ -3,8 +3,30 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Заметки</title>
|
||||
<title>Заметки - NoteJS</title>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<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" />
|
||||
<meta name="msapplication-TileColor" content="#007bff" />
|
||||
|
||||
<!-- 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" />
|
||||
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
|
||||
|
||||
<!-- Manifest -->
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="/style.css?v=4" />
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iconify/2.0.0/iconify.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@ -240,6 +262,10 @@
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/11.1.0/marked.min.js"></script>
|
||||
<script src="/app.js"></script>
|
||||
|
||||
<!-- PWA Script -->
|
||||
<script src="/pwa.js"></script>
|
||||
|
||||
<script>
|
||||
// Проверяем загрузку Iconify
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
@ -3,8 +3,30 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Личный кабинет</title>
|
||||
<title>Личный кабинет - NoteJS</title>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<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" />
|
||||
<meta name="msapplication-TileColor" content="#007bff" />
|
||||
|
||||
<!-- 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" />
|
||||
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
|
||||
|
||||
<!-- Manifest -->
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="/style.css?v=3" />
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iconify/2.0.0/iconify.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@ -132,5 +154,8 @@
|
||||
</div>
|
||||
|
||||
<script src="/profile.js"></script>
|
||||
|
||||
<!-- PWA Script -->
|
||||
<script src="/pwa.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
164
public/pwa.js
Normal file
164
public/pwa.js
Normal file
@ -0,0 +1,164 @@
|
||||
// PWA Service Worker Registration и установка
|
||||
class PWAManager {
|
||||
constructor() {
|
||||
this.deferredPrompt = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.registerServiceWorker();
|
||||
this.setupInstallPrompt();
|
||||
this.setupAppInstalled();
|
||||
}
|
||||
|
||||
// Регистрация 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Показ уведомления об обновлении
|
||||
showUpdateNotification() {
|
||||
if (confirm('Доступна новая версия приложения. Обновить?')) {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
navigator.serviceWorker.controller.postMessage({ type: 'SKIP_WAITING' });
|
||||
}
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
// Настройка промпта установки
|
||||
setupInstallPrompt() {
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
e.preventDefault();
|
||||
this.deferredPrompt = e;
|
||||
this.showInstallButton();
|
||||
});
|
||||
}
|
||||
|
||||
// Показ кнопки установки
|
||||
showInstallButton() {
|
||||
// Проверяем, не установлено ли уже приложение
|
||||
if (window.matchMedia('(display-mode: standalone)').matches ||
|
||||
window.navigator.standalone === true) {
|
||||
return; // Приложение уже установлено
|
||||
}
|
||||
|
||||
const installButton = this.createInstallButton();
|
||||
this.addInstallButtonToPage(installButton);
|
||||
}
|
||||
|
||||
// Создание кнопки установки
|
||||
createInstallButton() {
|
||||
const installButton = document.createElement('button');
|
||||
installButton.textContent = '📱 Установить приложение';
|
||||
installButton.className = 'btnSave';
|
||||
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.addEventListener('click', () => {
|
||||
this.installApp();
|
||||
});
|
||||
|
||||
return installButton;
|
||||
}
|
||||
|
||||
// Добавление кнопки на страницу
|
||||
addInstallButtonToPage(installButton) {
|
||||
// Ищем подходящее место для кнопки
|
||||
const authLink = document.querySelector('.auth-link');
|
||||
const footer = document.querySelector('.footer');
|
||||
const container = document.querySelector('.container');
|
||||
|
||||
if (authLink) {
|
||||
authLink.appendChild(installButton);
|
||||
} else if (footer) {
|
||||
footer.insertBefore(installButton, footer.firstChild);
|
||||
} else if (container) {
|
||||
container.appendChild(installButton);
|
||||
}
|
||||
}
|
||||
|
||||
// Установка приложения
|
||||
installApp() {
|
||||
if (this.deferredPrompt) {
|
||||
this.deferredPrompt.prompt();
|
||||
this.deferredPrompt.userChoice.then((choiceResult) => {
|
||||
if (choiceResult.outcome === 'accepted') {
|
||||
console.log('Пользователь установил приложение');
|
||||
this.trackInstallation();
|
||||
}
|
||||
this.deferredPrompt = null;
|
||||
this.removeInstallButton();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление кнопки установки
|
||||
removeInstallButton() {
|
||||
const installButton = document.querySelector('button[style*="Установить приложение"]');
|
||||
if (installButton) {
|
||||
installButton.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Отслеживание установки
|
||||
trackInstallation() {
|
||||
// Здесь можно добавить аналитику
|
||||
console.log('PWA установлено успешно');
|
||||
}
|
||||
|
||||
// Обработка успешной установки
|
||||
setupAppInstalled() {
|
||||
window.addEventListener('appinstalled', () => {
|
||||
console.log('PWA установлено успешно');
|
||||
this.removeInstallButton();
|
||||
});
|
||||
}
|
||||
|
||||
// Проверка статуса PWA
|
||||
isPWAInstalled() {
|
||||
return window.matchMedia('(display-mode: standalone)').matches ||
|
||||
window.navigator.standalone === true;
|
||||
}
|
||||
|
||||
// Получение информации о PWA
|
||||
getPWAInfo() {
|
||||
return {
|
||||
isInstalled: this.isPWAInstalled(),
|
||||
isOnline: navigator.onLine,
|
||||
hasServiceWorker: 'serviceWorker' in navigator,
|
||||
userAgent: navigator.userAgent
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация PWA Manager
|
||||
const pwaManager = new PWAManager();
|
||||
|
||||
// Экспорт для использования в других скриптах
|
||||
window.PWAManager = pwaManager;
|
||||
@ -3,8 +3,30 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Регистрация</title>
|
||||
<title>Регистрация - NoteJS</title>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря" />
|
||||
<meta name="theme-color" content="#007bff" />
|
||||
<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" />
|
||||
<meta name="msapplication-TileColor" content="#007bff" />
|
||||
|
||||
<!-- 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" />
|
||||
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
|
||||
|
||||
<!-- Manifest -->
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iconify/2.0.0/iconify.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@ -61,5 +83,8 @@
|
||||
<p>Создатель: <span>Fovway</span></p>
|
||||
</div>
|
||||
<script src="/register.js"></script>
|
||||
|
||||
<!-- PWA Script -->
|
||||
<script src="/pwa.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
266
public/sw.js
Normal file
266
public/sw.js
Normal file
@ -0,0 +1,266 @@
|
||||
// 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 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/'
|
||||
];
|
||||
|
||||
// Установка Service Worker
|
||||
self.addEventListener('install', (event) => {
|
||||
console.log('[SW] Установка Service Worker');
|
||||
|
||||
event.waitUntil(
|
||||
caches.open(STATIC_CACHE_NAME)
|
||||
.then((cache) => {
|
||||
console.log('[SW] Кэширование статических файлов');
|
||||
return cache.addAll(STATIC_FILES);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('[SW] Статические файлы закэшированы');
|
||||
return self.skipWaiting();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[SW] Ошибка при кэшировании статических файлов:', error);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Активация Service Worker
|
||||
self.addEventListener('activate', (event) => {
|
||||
console.log('[SW] Активация Service Worker');
|
||||
|
||||
event.waitUntil(
|
||||
caches.keys()
|
||||
.then((cacheNames) => {
|
||||
return Promise.all(
|
||||
cacheNames.map((cacheName) => {
|
||||
// Удаляем старые кэши
|
||||
if (cacheName !== STATIC_CACHE_NAME && cacheName !== DYNAMIC_CACHE_NAME) {
|
||||
console.log('[SW] Удаление старого кэша:', cacheName);
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('[SW] Service Worker активирован');
|
||||
return self.clients.claim();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Перехват запросов
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const { request } = event;
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Пропускаем запросы к API и загрузкам
|
||||
if (EXCLUDE_FROM_CACHE.some(pattern => url.pathname.startsWith(pattern))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Стратегия кэширования: Cache First для статических файлов, Network First для HTML
|
||||
if (request.method === 'GET') {
|
||||
event.respondWith(
|
||||
handleRequest(request)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Для статических ресурсов используем 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);
|
||||
const networkResponse = await fetch(request);
|
||||
|
||||
// Кэшируем успешные ответы
|
||||
if (networkResponse.ok) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE_NAME);
|
||||
cache.put(request, networkResponse.clone());
|
||||
}
|
||||
|
||||
return networkResponse;
|
||||
|
||||
} catch (error) {
|
||||
console.log('[SW] Сеть недоступна, поиск в кэше:', request.url);
|
||||
const cachedResponse = await caches.match(request);
|
||||
|
||||
if (cachedResponse) {
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
// Fallback на главную страницу
|
||||
if (request.headers.get('accept')?.includes('text/html')) {
|
||||
return await caches.match('/index.html');
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
|
||||
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] Очистка старых кэшей завершена');
|
||||
}
|
||||
19
server.js
19
server.js
@ -132,6 +132,25 @@ app.use(
|
||||
// Статические файлы
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
|
||||
// PWA файлы с правильными заголовками
|
||||
app.get('/manifest.json', (req, res) => {
|
||||
res.setHeader('Content-Type', 'application/manifest+json');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400'); // 24 часа
|
||||
res.sendFile(path.join(__dirname, 'public', 'manifest.json'));
|
||||
});
|
||||
|
||||
app.get('/sw.js', (req, res) => {
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
res.sendFile(path.join(__dirname, 'public', 'sw.js'));
|
||||
});
|
||||
|
||||
app.get('/browserconfig.xml', (req, res) => {
|
||||
res.setHeader('Content-Type', 'application/xml');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400'); // 24 часа
|
||||
res.sendFile(path.join(__dirname, 'public', 'browserconfig.xml'));
|
||||
});
|
||||
|
||||
// Парсинг тела запроса
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user