This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background-color: #121212;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 5px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-top-color: #ffffff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #007bff;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="theme-color" content="#121212" />
|
||||||
|
<title>Загрузка… — НаСвязи</title>
|
||||||
|
<link rel="stylesheet" href="/css/offline.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="loader">
|
||||||
|
<div id="offline-message" style="display: none;">
|
||||||
|
<h1>Нет подключения к интернету</h1>
|
||||||
|
<p>Проверьте подключение к сети или попробуйте ещё раз.</p>
|
||||||
|
<button id="retry-button">Попробовать снова</button>
|
||||||
|
</div>
|
||||||
|
<div class="spinner" style="display: none;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const offlineMessage = document.getElementById('offline-message');
|
||||||
|
const spinner = document.querySelector('.spinner');
|
||||||
|
const retryButton = document.getElementById('retry-button');
|
||||||
|
|
||||||
|
function setTitle(message) {
|
||||||
|
document.title = `${message} — НаСвязи`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showOfflineMessage() {
|
||||||
|
offlineMessage.style.display = 'block';
|
||||||
|
spinner.style.display = 'none';
|
||||||
|
setTitle('Проблемы с подключением');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoadingMessage() {
|
||||||
|
offlineMessage.style.display = 'none';
|
||||||
|
spinner.style.display = 'block';
|
||||||
|
setTitle('Загрузка…');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function retryConnection() {
|
||||||
|
showLoadingMessage();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/', { method: 'HEAD' });
|
||||||
|
if (response.ok) {
|
||||||
|
location.reload(); // Перезагружаем страницу при успешном подключении
|
||||||
|
} else {
|
||||||
|
throw new Error('Сервер не отвечает');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
showOfflineMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOnlineStatus() {
|
||||||
|
location.reload(); // Перезагрузка при восстановлении сети
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkConnectionWithServiceWorker() {
|
||||||
|
if (!navigator.onLine) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const messageChannel = new MessageChannel();
|
||||||
|
|
||||||
|
// Обрабатываем ответ от сервис-воркера
|
||||||
|
messageChannel.port1.onmessage = (event) => {
|
||||||
|
resolve(event.data.online);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Посылаем сообщение в сервис-воркер
|
||||||
|
navigator.serviceWorker.controller.postMessage(
|
||||||
|
{ type: 'CHECK_ONLINE_STATUS' },
|
||||||
|
[messageChannel.port2]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Если сервис-воркер недоступен, возвращаем состояние navigator.onLine
|
||||||
|
return navigator.onLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkConnectionWithServiceWorker().then((isOnline) => {
|
||||||
|
if (!isOnline) showOfflineMessage();
|
||||||
|
})
|
||||||
|
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.onmessage = (event) => {
|
||||||
|
console.log('onmessage', event.data && event.data.type)
|
||||||
|
if (event.data && event.data.type === 'MAIN_PAGE_READY') {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.data && event.data.type === 'NETWORK_ERROR') {
|
||||||
|
showOfflineMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
retryButton.addEventListener('click', retryConnection);
|
||||||
|
window.addEventListener('online', handleOnlineStatus);
|
||||||
|
addEventListener("offline", showOfflineMessage);
|
||||||
|
|
||||||
|
|
||||||
|
showLoadingMessage();
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
if (!navigator.onLine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
|
||||||
|
navigator.serviceWorker.controller.postMessage({ type: 'UPDATE_STATIC_ASSETS' });
|
||||||
|
console.log('Message sent to Service Worker: UPDATE_STATIC_ASSETS');
|
||||||
|
} else {
|
||||||
|
console.warn('Service Worker is not available or not controlling this page.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,25 +1,155 @@
|
|||||||
// const VER = 14
|
// const VER = 18
|
||||||
|
|
||||||
self.addEventListener('message', async (event) => {
|
const CACHE_NAME = 'cache-v1'
|
||||||
event.waitUntil(debug(Object.assign({ answer: true }, event.data)))
|
const TEMP_PAGE_URL = '/offline.html'
|
||||||
})
|
const STATIC_ASSETS = [TEMP_PAGE_URL, '/css/offline.css']
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
self.addEventListener('install', (event) => {
|
||||||
// The promise that skipWaiting() returns can be safely ignored.
|
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS)))
|
||||||
event.waitUntil(self.skipWaiting().then(() => debug({ installed: true })))
|
|
||||||
|
|
||||||
// Perform any other actions required for your
|
|
||||||
// service worker to install, potentially inside
|
|
||||||
// of event.waitUntil();
|
|
||||||
})
|
})
|
||||||
|
|
||||||
self.addEventListener('activate', (event) => {
|
self.addEventListener('activate', (event) => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
// Привязка нового сервис-воркера к текущим клиентам
|
caches.keys().then((cacheNames) =>
|
||||||
clients.claim().then(() => debug(event.source, { activated: true })),
|
Promise.all(
|
||||||
|
cacheNames.map((cache) => {
|
||||||
|
if (cache !== CACHE_NAME) {
|
||||||
|
return caches.delete(cache)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
self.addEventListener('fetch', (event) => {
|
||||||
|
const url = new URL(event.request.url)
|
||||||
|
|
||||||
|
// Обрабатываем только HTTP/HTTPS запросы
|
||||||
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.request.mode === 'navigate') {
|
||||||
|
// Для навигационных запросов сначала отдаём `/offline.html`, пока основная страница загружается
|
||||||
|
event.respondWith(handleNavigationRequest(event.request))
|
||||||
|
} else if (STATIC_ASSETS.includes(url.pathname)) {
|
||||||
|
// Кэшируем только offline.html и его CSS
|
||||||
|
event.respondWith(caches.match(event.request))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const TEMP_CACHE = new Map() // Используем карту для временного хранения
|
||||||
|
|
||||||
|
async function handleNavigationRequest(request) {
|
||||||
|
if (TEMP_CACHE.has(request.url) && navigator.onLine) {
|
||||||
|
return TEMP_CACHE.get(request.url).clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
const cache = await caches.open(CACHE_NAME)
|
||||||
|
|
||||||
|
// Отдаём временную страницу из кеша
|
||||||
|
const tempResponse = await cache.match(TEMP_PAGE_URL)
|
||||||
|
|
||||||
|
// Попытка загрузить основную страницу в фоновом режиме
|
||||||
|
fetch(request)
|
||||||
|
.then(async (networkResponse) => {
|
||||||
|
if (networkResponse && networkResponse.ok) {
|
||||||
|
// Сохраняем клон в TEMP_CACHE
|
||||||
|
TEMP_CACHE.set(request.url, networkResponse)
|
||||||
|
|
||||||
|
// Сообщаем клиенту, что основная страница готова
|
||||||
|
const allClients = await clients.matchAll({ type: 'window' })
|
||||||
|
allClients.forEach((client) =>
|
||||||
|
client.postMessage({
|
||||||
|
type: 'MAIN_PAGE_READY',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Удаляем из TEMP_CACHE через минимальное время
|
||||||
|
setTimeout(() => {
|
||||||
|
TEMP_CACHE.delete(request.url)
|
||||||
|
}, 5000)
|
||||||
|
} else {
|
||||||
|
throw new Error('Failed to fetch main page')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(async (error) => {
|
||||||
|
console.error('Error loading main page:', error)
|
||||||
|
|
||||||
|
// Сообщаем клиенту об ошибке сети
|
||||||
|
const allClients = await clients.matchAll({ type: 'window' })
|
||||||
|
allClients.forEach((client) =>
|
||||||
|
client.postMessage({
|
||||||
|
type: 'NETWORK_ERROR',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Возвращаем временную страницу
|
||||||
|
return tempResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateStaticAssetsCache() {
|
||||||
|
const cache = await caches.open(CACHE_NAME)
|
||||||
|
|
||||||
|
// Обновляем ресурсы
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
STATIC_ASSETS.map(async (asset) => {
|
||||||
|
const response = await fetch(asset, { cache: 'no-store' })
|
||||||
|
if (response.ok) {
|
||||||
|
await cache.put(asset, response.clone())
|
||||||
|
} else {
|
||||||
|
console.error(`Failed to fetch ${asset}:`, response.status)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
console.log('STATIC_ASSETS обновлены.')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка обновления STATIC_ASSETS:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener('message', async (event) => {
|
||||||
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||||
|
self.skipWaiting()
|
||||||
|
} else if (event.data && event.data.type === 'CHECK_ONLINE_STATUS') {
|
||||||
|
const isOnline = navigator.onLine // Проверяем состояние сети
|
||||||
|
event.ports[0].postMessage({ online: isOnline }) // Отправляем результат обратно
|
||||||
|
} else if (event.data && event.data.type === 'UPDATE_STATIC_ASSETS') {
|
||||||
|
event.waitUntil(updateStaticAssetsCache())
|
||||||
|
} else {
|
||||||
|
event.waitUntil(debug(Object.assign({ answer: true }, event.data)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.addEventListener('install', (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE_NAME).then((cache) => {
|
||||||
|
return cache.addAll(STATIC_ASSETS)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.skipWaiting().then(() => debug({ installed: true }))
|
||||||
|
})
|
||||||
|
|
||||||
|
self.addEventListener('activate', (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys().then((cacheNames) =>
|
||||||
|
Promise.all(
|
||||||
|
cacheNames.map((cache) => {
|
||||||
|
if (cache !== CACHE_NAME) {
|
||||||
|
return caches.delete(cache)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
clients.claim().then(() => debug(event.source, { activated: true }))
|
||||||
|
})
|
||||||
|
|
||||||
self.addEventListener('push', async (event) => {
|
self.addEventListener('push', async (event) => {
|
||||||
debug({
|
debug({
|
||||||
event: 'push',
|
event: 'push',
|
||||||
|
|||||||
@@ -13,7 +13,15 @@ const debug = (...data: unknown[]) => {
|
|||||||
|
|
||||||
provide('debug', debug)
|
provide('debug', debug)
|
||||||
|
|
||||||
registerServiceWorker()
|
const serviceWorkerPromise = registerServiceWorker()
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await serviceWorkerPromise
|
||||||
|
navigator.serviceWorker?.controller?.postMessage({
|
||||||
|
type: 'UPDATE_STATIC_ASSETS',
|
||||||
|
})
|
||||||
|
console.log('Message sent to Service Worker: UPDATE_STATIC_ASSETS')
|
||||||
|
})
|
||||||
// .then(() => {
|
// .then(() => {
|
||||||
// // console.log('Service Worker успешно зарегистрирован')
|
// // console.log('Service Worker успешно зарегистрирован')
|
||||||
// navigator.serviceWorker.addEventListener('message', (event) => {
|
// navigator.serviceWorker.addEventListener('message', (event) => {
|
||||||
|
|||||||
@@ -22,12 +22,12 @@
|
|||||||
<span> </span>
|
<span> </span>
|
||||||
<span class="text-nowrap">
|
<span class="text-nowrap">
|
||||||
Будем рады
|
Будем рады
|
||||||
<a
|
<RouterLink
|
||||||
href="/donate"
|
to="/donate"
|
||||||
class="underline underline-offset-2 text-primary-600 dark:text-primary-400"
|
class="underline underline-offset-2 text-primary-600 dark:text-primary-400"
|
||||||
>
|
>
|
||||||
вашей поддержке
|
вашей поддержке
|
||||||
</a>
|
</RouterLink>
|
||||||
</span>
|
</span>
|
||||||
</footer>
|
</footer>
|
||||||
</slot>
|
</slot>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export async function registerServiceWorker() {
|
|||||||
// },
|
// },
|
||||||
// {},
|
// {},
|
||||||
// )
|
// )
|
||||||
//
|
|
||||||
return registration
|
return registration
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error('Ошибка регистрации Service Worker:', error)
|
// console.error('Ошибка регистрации Service Worker:', error)
|
||||||
|
|||||||
@@ -27,17 +27,17 @@
|
|||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Нужен такой же?
|
Нужен такой же?
|
||||||
<a
|
<RouterLink
|
||||||
href="/create"
|
to="/create"
|
||||||
class="underline underline-offset-2 text-primary-600 dark:text-primary-400"
|
class="underline underline-offset-2 text-primary-600 dark:text-primary-400"
|
||||||
>Создайте свой QR‑код</a
|
>Создайте свой QR‑код</RouterLink
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1">
|
<p class="mt-1">
|
||||||
Сделайте НаСвязи удобнее. Будем рады вашей <a
|
Сделайте НаСвязи удобнее. Будем рады вашей <RouterLink
|
||||||
href="/donate"
|
to="/donate"
|
||||||
class="underline underline-offset-2 text-primary-600 dark:text-primary-400"
|
class="underline underline-offset-2 text-primary-600 dark:text-primary-400"
|
||||||
>поддержке</a
|
>поддержке</RouterLink
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
Reference in New Issue
Block a user