NoteJS/public/pwa.js
Fovway 091fc6cc1e Обновлены мета-теги и улучшена функциональность PWA
- Оптимизированы мета-теги в index.html и test-pwa.html для лучшей поддержки PWA.
- Улучшена структура кода с использованием многострочных атрибутов для мета-тегов.
- Обновлен сервисный работник для более эффективного кэширования и обработки запросов.
- Добавлены новые функции в pwa.js для управления установкой и обновлением PWA.
2025-10-20 12:29:43 +07:00

648 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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